Skip to content

Commit 9b78620

Browse files
committed
[Basic] Add a JSON value type.
- This isn't intended to be a full fledged JSON infrastructure, but we have a need to persist (relatively small) things to disk, and so we need some kind of minimal archiving and encoding infrastructure.
1 parent ec6da86 commit 9b78620

File tree

3 files changed

+165
-0
lines changed

3 files changed

+165
-0
lines changed

Sources/Basic/JSON.swift

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2016 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
10+
-------------------------------------------------------------------------
11+
12+
This file defines JSON support infrastructure. It is not designed to be general
13+
purpose JSON utilities, but rather just the infrastructure which SwiftPM needs
14+
to manage serialization of data through JSON.
15+
*/
16+
17+
// MARK: JSON Item Definition
18+
19+
/// A JSON value.
20+
///
21+
/// This type uses container wrappers in order to allow for mutable elements.
22+
public enum JSON {
23+
/// The null value.
24+
case null
25+
26+
/// A boolean value.
27+
case bool(Bool)
28+
29+
/// An integer value.
30+
///
31+
/// While not strictly present in JSON, we use this as a convenience to
32+
/// parsing code.
33+
case int(Int)
34+
35+
/// A floating-point value.
36+
case double(Double)
37+
38+
/// A string.
39+
case string(String)
40+
41+
/// An array.
42+
case array([JSON])
43+
44+
/// A dictionary.
45+
case dictionary([String: JSON])
46+
}
47+
48+
extension JSON: CustomStringConvertible {
49+
public var description: Swift.String {
50+
switch self {
51+
case .null: return "null"
52+
case .bool(let value): return value.description
53+
case .int(let value): return value.description
54+
case .double(let value): return value.description
55+
case .string(let value): return value.debugDescription
56+
case .array(let values): return values.description
57+
case .dictionary(let values): return values.description
58+
}
59+
}
60+
}
61+
62+
/// Equatable conformance.
63+
extension JSON: Equatable { }
64+
public func ==(lhs: JSON, rhs: JSON) -> Bool {
65+
switch (lhs, rhs) {
66+
case (.null, .null): return true
67+
case (.null, _): return false
68+
case (.bool(let a), .bool(let b)): return a == b
69+
case (.bool, _): return false
70+
case (.int(let a), .int(let b)): return a == b
71+
case (.int, _): return false
72+
case (.double(let a), .double(let b)): return a == b
73+
case (.double, _): return false
74+
case (.string(let a), .string(let b)): return a == b
75+
case (.string, _): return false
76+
case (.array(let a), .array(let b)): return a == b
77+
case (.array, _): return false
78+
case (.dictionary(let a), .dictionary(let b)): return a == b
79+
case (.dictionary, _): return false
80+
}
81+
}
82+
83+
// MARK: JSON Encoding
84+
85+
extension JSON {
86+
/// Encode a JSON item into a string of bytes.
87+
public func toBytes() -> ByteString {
88+
return (OutputByteStream() <<< self).bytes
89+
}
90+
91+
}
92+
93+
/// Support writing to a byte stream.
94+
extension JSON: ByteStreamable {
95+
public func write(to stream: OutputByteStream) {
96+
switch self {
97+
case .null:
98+
stream <<< "null"
99+
case .bool(let value):
100+
stream <<< Format.asJSON(value)
101+
case .int(let value):
102+
stream <<< Format.asJSON(value)
103+
case .double(let value):
104+
// FIXME: What happens for NaN, etc.?
105+
stream <<< Format.asJSON(value)
106+
case .string(let value):
107+
stream <<< Format.asJSON(value)
108+
case .array(let contents):
109+
// FIXME: OutputByteStream should just let us do this via conformances.
110+
stream <<< "["
111+
for (i, item) in contents.enumerated() {
112+
if i != 0 { stream <<< ", " }
113+
stream <<< item
114+
}
115+
stream <<< "]"
116+
case .dictionary(let contents):
117+
// We always output in a deterministic order.
118+
//
119+
// FIXME: OutputByteStream should just let us do this via conformances.
120+
stream <<< "{"
121+
for (i, key) in contents.keys.sorted().enumerated() {
122+
if i != 0 { stream <<< ", " }
123+
stream <<< Format.asJSON(key) <<< ": " <<< contents[key]!
124+
}
125+
stream <<< "}"
126+
}
127+
}
128+
}

Tests/Basic/JSONTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2015 - 2016 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import XCTest
12+
13+
@testable import Basic
14+
15+
class JSONTests: XCTestCase {
16+
func testEncoding() {
17+
// Test the basics of encoding each object type.
18+
func encode(_ item: JSON) -> String {
19+
return item.toBytes().asString ?? "<unrepresentable>"
20+
}
21+
22+
XCTAssertEqual(encode(.bool(false)), "false")
23+
XCTAssertEqual(encode(.int(1)), "1")
24+
XCTAssertEqual(encode(.string("hi")), "\"hi\"")
25+
XCTAssertEqual(encode(.array([.int(1), .string("hi")])), "[1, \"hi\"]")
26+
XCTAssertEqual(encode(.dictionary(["a": .int(1), "b": .string("hi")])), "{\"a\": 1, \"b\": \"hi\"}")
27+
}
28+
}
29+
30+
extension JSONTests {
31+
static var allTests: [(String, (JSONTests) -> () throws -> Void)] {
32+
return [
33+
("testEncoding", testEncoding),
34+
]
35+
}
36+
}

Tests/Basic/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import XCTest
1414
public func allTests() -> [XCTestCaseEntry] {
1515
return [
1616
testCase(ByteStringTests.allTests),
17+
testCase(JSONTests.allTests),
1718
testCase(OptionParserTests.allTests),
1819
testCase(OutputByteStreamTests.allTests),
1920
testCase(TOMLTests.allTests),

0 commit comments

Comments
 (0)