Skip to content

Commit af21742

Browse files
authored
SWIFT-960 Support outputFormatting options in ExtendedJSONEncoder (#38)
This commit also adds a CustomStringConvertible conformance to BSONDocument
1 parent a7389e3 commit af21742

File tree

3 files changed

+80
-3
lines changed

3 files changed

+80
-3
lines changed

Sources/BSON/BSONDocument.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ public struct BSONDocument {
142142
/// On error, an empty string will be returned.
143143
public func toExtendedJSONString() -> String {
144144
let encoder = ExtendedJSONEncoder()
145+
encoder.outputFormatting = [.prettyPrinted]
145146
guard let encoded = try? encoder.encode(self) else {
146147
return ""
147148
}
@@ -153,6 +154,7 @@ public struct BSONDocument {
153154
public func toCanonicalExtendedJSONString() -> String {
154155
let encoder = ExtendedJSONEncoder()
155156
encoder.mode = .canonical
157+
encoder.outputFormatting = [.prettyPrinted]
156158
guard let encoded = try? encoder.encode(self) else {
157159
return ""
158160
}
@@ -462,3 +464,7 @@ extension BSONDocument: BSONValue {
462464
buffer.writeBuffer(&doc)
463465
}
464466
}
467+
468+
extension BSONDocument: CustomStringConvertible {
469+
public var description: String { self.toExtendedJSONString() }
470+
}

Sources/BSON/ExtendedJSONEncoder.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,36 @@ public class ExtendedJSONEncoder {
1414
case relaxed
1515
}
1616

17+
/// The output formatting options that determine the readability, size, and element order of an encoded JSON object.
18+
public struct OutputFormatting: OptionSet {
19+
internal let value: JSONEncoder.OutputFormatting
20+
21+
public var rawValue: UInt { self.value.rawValue }
22+
23+
public init(rawValue: UInt) {
24+
self.value = JSONEncoder.OutputFormatting(rawValue: rawValue)
25+
}
26+
27+
internal init(_ value: JSONEncoder.OutputFormatting) {
28+
self.value = value
29+
}
30+
31+
/// Produce human-readable JSON with indented output.
32+
public static let prettyPrinted = OutputFormatting(.prettyPrinted)
33+
34+
/// Produce JSON with dictionary keys sorted in lexicographic order.
35+
public static let sortedKeys = OutputFormatting(.sortedKeys)
36+
}
37+
1738
/// Determines whether to encode to canonical or relaxed extended JSON. Default is relaxed.
1839
public var mode: Mode = .relaxed
1940

2041
/// Contextual user-provided information for use during encoding.
2142
public var userInfo: [CodingUserInfoKey: Any] = [:]
2243

44+
/// A value that determines the readability, size, and element order of the encoded JSON object.
45+
public var outputFormatting: ExtendedJSONEncoder.OutputFormatting = []
46+
2347
/// Initialize an `ExtendedJSONEncoder`.
2448
public init() {}
2549

@@ -50,6 +74,8 @@ public class ExtendedJSONEncoder {
5074
json = bson.bsonValue.toRelaxedExtendedJSON()
5175
}
5276

53-
return try JSONEncoder().encode(json)
77+
let jsonEncoder = JSONEncoder()
78+
jsonEncoder.outputFormatting = self.outputFormatting.value
79+
return try jsonEncoder.encode(json)
5480
}
5581
}

Tests/BSONTests/ExtendedJSONConversionTests.swift

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,41 @@ open class ExtendedJSONConversionTestCase: BSONTestCase {
103103
expect(String(data: fooEncoded, encoding: .utf8)).to(contain("false"))
104104
}
105105

106+
func testOutputFormatting() throws {
107+
let encoder = ExtendedJSONEncoder()
108+
let input: BSONDocument = ["topLevel": ["hello": "world"]]
109+
110+
let defaultFormat = String(data: try encoder.encode(input), encoding: .utf8)
111+
expect(defaultFormat).to(equal("{\"topLevel\":{\"hello\":\"world\"}}"))
112+
113+
encoder.outputFormatting = [.prettyPrinted]
114+
let prettyPrint = String(data: try encoder.encode(input), encoding: .utf8)
115+
let prettyOutput = """
116+
{
117+
"topLevel" : {
118+
"hello" : "world"
119+
}
120+
}
121+
"""
122+
expect(prettyPrint).to(equal(prettyOutput))
123+
124+
let multiKeyInput: BSONDocument = ["x": 1, "a": 2]
125+
126+
encoder.outputFormatting = [.sortedKeys]
127+
let sorted = String(data: try encoder.encode(multiKeyInput), encoding: .utf8)
128+
expect(sorted).to(equal("{\"a\":2,\"x\":1}"))
129+
130+
encoder.outputFormatting = [.sortedKeys, .prettyPrinted]
131+
let both = String(data: try encoder.encode(multiKeyInput), encoding: .utf8)
132+
let sortedPrettyOutput = """
133+
{
134+
\"a\" : 2,
135+
\"x\" : 1
136+
}
137+
"""
138+
expect(both).to(equal(sortedPrettyOutput))
139+
}
140+
106141
func testAnyExtJSON() throws {
107142
// Success cases
108143
expect(try BSON(fromExtJSON: "hello", keyPath: [])).to(equal(BSON.string("hello")))
@@ -303,8 +338,18 @@ open class ExtendedJSONConversionTestCase: BSONTestCase {
303338
expect(try BSONDocument(fromJSON: "{\"key\": {\"$numberInt\": \"5\"}}".data(using: .utf8)!))
304339
.to(equal(["key": .int32(5)]))
305340

306-
let canonicalExtJSON = "{\"key\":{\"$numberInt\":\"5\"}}"
307-
let relaxedExtJSON = "{\"key\":5}"
341+
let canonicalExtJSON = """
342+
{
343+
"key" : {
344+
"$numberInt" : "5"
345+
}
346+
}
347+
"""
348+
let relaxedExtJSON = """
349+
{
350+
"key" : 5
351+
}
352+
"""
308353
let canonicalDoc = try BSONDocument(fromJSON: canonicalExtJSON)
309354
let relaxedDoc = try BSONDocument(fromJSON: relaxedExtJSON)
310355
expect(canonicalDoc).to(equal(["key": .int32(5)]))

0 commit comments

Comments
 (0)