Skip to content

Commit 92f2f45

Browse files
authored
Encoding fixes. (#25)
Thanks so much @adam-fowler
1 parent 9071a99 commit 92f2f45

File tree

6 files changed

+284
-27
lines changed

6 files changed

+284
-27
lines changed

Sources/PureSwiftJSONCoding/Encoding/JSONEncoder.swift

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,102 @@
11
import PureSwiftJSONParsing
22

3+
enum JSONFuture {
4+
case value(JSONValue)
5+
case nestedArray(JSONArray)
6+
case nestedObject(JSONObject)
7+
}
8+
39
class JSONArray {
410

5-
private(set) var array: [JSONValue] = []
11+
private(set) var array: [JSONFuture] = []
612

713
init() {
814
array.reserveCapacity(10)
915
}
1016

1117
@inline(__always) func append(_ element: JSONValue) {
12-
self.array.append(element)
18+
self.array.append(.value(element))
19+
}
20+
21+
@inline(__always) func appendArray() -> JSONArray {
22+
let array = JSONArray()
23+
self.array.append(.nestedArray(array))
24+
return array
25+
}
26+
27+
@inline(__always) func appendObject() -> JSONObject {
28+
let object = JSONObject()
29+
self.array.append(.nestedObject(object))
30+
return object
31+
}
32+
33+
var values: [JSONValue] {
34+
self.array.map { (future) -> JSONValue in
35+
switch future {
36+
case .value(let value):
37+
return value
38+
case .nestedArray(let array):
39+
return .array(array.values)
40+
case .nestedObject(let object):
41+
return .object(object.values)
42+
}
43+
}
1344
}
1445
}
1546

1647
class JSONObject {
1748

18-
private(set) var dict: [String: JSONValue] = [:]
49+
private(set) var dict: [String: JSONFuture] = [:]
1950

2051
init() {
2152
dict.reserveCapacity(20)
2253
}
2354

2455
@inline(__always) func set(_ value: JSONValue, for key: String) {
25-
self.dict[key] = value
56+
self.dict[key] = .value(value)
57+
}
58+
59+
@inline(__always) func setArray(for key: String) -> JSONArray {
60+
if case .nestedArray(let array) = self.dict[key] {
61+
return array
62+
}
63+
64+
if case .nestedObject(_) = self.dict[key] {
65+
preconditionFailure("For key \"\(key)\" a keyed container has already been created.")
66+
}
67+
68+
let array = JSONArray()
69+
self.dict[key] = .nestedArray(array)
70+
return array
2671
}
72+
73+
@inline(__always) func setObject(for key: String) -> JSONObject {
74+
if case .nestedObject(let object) = self.dict[key] {
75+
return object
76+
}
77+
78+
if case .nestedArray(_) = self.dict[key] {
79+
preconditionFailure("For key \"\(key)\" an unkeyed container has already been created.")
80+
}
81+
82+
let object = JSONObject()
83+
self.dict[key] = .nestedObject(object)
84+
return object
85+
}
86+
87+
var values: [String: JSONValue] {
88+
self.dict.mapValues { (future) -> JSONValue in
89+
switch future {
90+
case .value(let value):
91+
return value
92+
case .nestedArray(let array):
93+
return .array(array.values)
94+
case .nestedObject(let object):
95+
return .object(object.values)
96+
}
97+
}
98+
}
99+
27100
}
28101

29102
public class JSONEncoder {
@@ -62,17 +135,17 @@ class JSONEncoderImpl {
62135

63136
var value: JSONValue? {
64137
if let object = self.object {
65-
return .object(object.dict)
138+
return .object(object.values)
66139
}
67140
if let array = self.array {
68-
return .array(array.array)
141+
return .array(array.values)
69142
}
70143
return self.singleValue
71144
}
72145

73146
init(userInfo: [CodingUserInfoKey : Any], codingPath : [CodingKey]) {
74147
self.userInfo = userInfo
75-
self.codingPath = []
148+
self.codingPath = codingPath
76149
}
77150

78151
}
@@ -115,18 +188,3 @@ extension JSONEncoderImpl: Encoder {
115188
return JSONSingleValueEncodingContainer(impl: self, codingPath: codingPath)
116189
}
117190
}
118-
119-
extension JSONEncoderImpl {
120-
121-
func nestedContainer<NestedKey, Key>(keyedBy keyType: NestedKey.Type, forKey key: Key)
122-
-> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey, Key : CodingKey
123-
{
124-
preconditionFailure("unimplemented")
125-
}
126-
127-
func nestedUnkeyedContainer<Key>(forKey key: Key)
128-
-> UnkeyedEncodingContainer where Key : CodingKey
129-
{
130-
preconditionFailure("unimplemented")
131-
}
132-
}

Sources/PureSwiftJSONCoding/Encoding/JSONKeyedEncodingContainer.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ struct JSONKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol
1313
self.object = impl.object!
1414
self.codingPath = codingPath
1515
}
16+
17+
// used for nested containers
18+
init(impl: JSONEncoderImpl, object: JSONObject, codingPath: [CodingKey]) {
19+
self.impl = impl
20+
self.object = object
21+
self.codingPath = codingPath
22+
}
1623

1724
mutating func encodeNil(forKey key: Self.Key) throws {
1825

@@ -89,11 +96,17 @@ struct JSONKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol
8996
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Self.Key) ->
9097
KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey
9198
{
92-
return impl.nestedContainer(keyedBy: keyType, forKey: key)
99+
let newPath = impl.codingPath + [key]
100+
let object = self.object.setObject(for: key.stringValue)
101+
let nestedContainer = JSONKeyedEncodingContainer<NestedKey>(impl: self.impl, object: object, codingPath: newPath)
102+
return KeyedEncodingContainer(nestedContainer)
93103
}
94104

95105
mutating func nestedUnkeyedContainer(forKey key: Self.Key) -> UnkeyedEncodingContainer {
96-
return impl.nestedUnkeyedContainer(forKey: key)
106+
let newPath = impl.codingPath + [key]
107+
let array = self.object.setArray(for: key.stringValue)
108+
let nestedContainer = JSONUnkeyedEncodingContainer(impl: self.impl, array: array, codingPath: newPath)
109+
return nestedContainer
97110
}
98111

99112
mutating func superEncoder() -> Encoder {

Sources/PureSwiftJSONCoding/Encoding/JSONUnkeyedEncodingContainer.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ struct JSONUnkeyedEncodingContainer: UnkeyedEncodingContainer {
1212
self.impl = impl
1313
self.array = impl.array!
1414
self.codingPath = codingPath
15-
15+
}
16+
17+
// used for nested containers
18+
init(impl: JSONEncoderImpl, array: JSONArray, codingPath: [CodingKey]) {
19+
self.impl = impl
20+
self.array = array
21+
self.codingPath = codingPath
1622
}
1723

1824
mutating func encodeNil() throws {
@@ -90,11 +96,17 @@ struct JSONUnkeyedEncodingContainer: UnkeyedEncodingContainer {
9096
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) ->
9197
KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey
9298
{
93-
preconditionFailure()
99+
let newPath = impl.codingPath + [ArrayKey(index: count)]
100+
let object = self.array.appendObject()
101+
let nestedContainer = JSONKeyedEncodingContainer<NestedKey>(impl: self.impl, object: object, codingPath: newPath)
102+
return KeyedEncodingContainer(nestedContainer)
94103
}
95104

96105
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
97-
preconditionFailure()
106+
let newPath = impl.codingPath + [ArrayKey(index: count)]
107+
let array = self.array.appendArray()
108+
let nestedContainer = JSONUnkeyedEncodingContainer(impl: self.impl, array: array, codingPath: newPath)
109+
return nestedContainer
98110
}
99111

100112
mutating func superEncoder() -> Encoder {

Tests/JSONCodingTests/Encoding/JSONEncoderTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,37 @@ class JSONEncoderTests: XCTestCase {
8484
}
8585
}
8686

87+
func testLastCodingPath() {
88+
struct SubObject: Encodable {
89+
let value: Int
90+
91+
func encode(to encoder: Encoder) throws {
92+
var container = encoder.container(keyedBy: CodingKeys.self)
93+
if let key = encoder.codingPath.last {
94+
try container.encode(key.stringValue, forKey: .key)
95+
try container.encode(value, forKey: .value)
96+
}
97+
}
98+
99+
private enum CodingKeys: String, CodingKey {
100+
case key = "key"
101+
case value = "value"
102+
}
103+
}
104+
105+
struct Object: Encodable {
106+
let sub: SubObject
107+
}
108+
109+
do {
110+
let object = Object(sub: SubObject(value: 12))
111+
let json = try PureSwiftJSONCoding.JSONEncoder().encode(object)
112+
let parsed = try JSONParser().parse(bytes: json)
113+
XCTAssertEqual(parsed, .object(["sub": .object(["key": .string("sub"), "value": .number("12")])]))
114+
}
115+
catch {
116+
XCTFail("Unexpected error: \(error)")
117+
}
118+
}
87119
}
88120

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import XCTest
2+
@testable import PureSwiftJSONCoding
3+
@testable import PureSwiftJSONParsing
4+
5+
class JSONKeyedEncodingContainerTests: XCTestCase {
6+
7+
func testNestedKeyedContainer() {
8+
struct Object: Encodable {
9+
let firstName: String
10+
let surname: String
11+
12+
func encode(to encoder: Encoder) throws {
13+
var container = encoder.container(keyedBy: CodingKeys.self)
14+
var nameContainer = container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
15+
try nameContainer.encode(firstName, forKey: .firstName)
16+
17+
var sameContainer = container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
18+
try sameContainer.encode(surname, forKey: .surname)
19+
}
20+
21+
private enum CodingKeys: String, CodingKey {
22+
case name = "name"
23+
}
24+
25+
private enum NameCodingKeys: String, CodingKey {
26+
case firstName = "firstName"
27+
case surname = "surname"
28+
}
29+
}
30+
31+
do {
32+
let object = Object(firstName: "Adam", surname: "Fowler")
33+
let json = try PureSwiftJSONCoding.JSONEncoder().encode(object)
34+
35+
let parsed = try JSONParser().parse(bytes: json)
36+
XCTAssertEqual(parsed, .object(["name": .object(["firstName": .string("Adam"), "surname": .string("Fowler")])]))
37+
}
38+
catch {
39+
XCTFail("Unexpected error: \(error)")
40+
}
41+
}
42+
43+
func testNestedUnkeyedContainer() {
44+
struct NumberStruct: Encodable {
45+
func encode(to encoder: Encoder) throws {
46+
var container = encoder.container(keyedBy: CodingKeys.self)
47+
var numbersContainer = container.nestedUnkeyedContainer(forKey: .numbers)
48+
var sameContainer = container.nestedUnkeyedContainer(forKey: .numbers)
49+
50+
try numbersContainer.encode(1)
51+
try sameContainer.encode(2)
52+
try numbersContainer.encode(3)
53+
try sameContainer.encode(4)
54+
}
55+
56+
private enum CodingKeys: String, CodingKey {
57+
case numbers
58+
}
59+
}
60+
61+
do {
62+
let object = NumberStruct()
63+
let json = try PureSwiftJSONCoding.JSONEncoder().encode(object)
64+
65+
let parsed = try JSONParser().parse(bytes: json)
66+
XCTAssertEqual(parsed, .object(["numbers": .array([.number("1"), .number("2"), .number("3"), .number("4")])]))
67+
}
68+
catch {
69+
XCTFail("Unexpected error: \(error)")
70+
}
71+
}
72+
73+
}
74+
75+
76+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import XCTest
2+
@testable import PureSwiftJSONCoding
3+
@testable import PureSwiftJSONParsing
4+
5+
class JSONUnkeyedEncodingContainerTests: XCTestCase {
6+
7+
func testNestedKeyedContainer() {
8+
struct ObjectInArray: Encodable {
9+
let firstName: String
10+
let surname: String
11+
12+
func encode(to encoder: Encoder) throws {
13+
var container = encoder.unkeyedContainer()
14+
var nestedContainer = container.nestedContainer(keyedBy: NameCodingKeys.self)
15+
try nestedContainer.encode(firstName, forKey: .firstName)
16+
try nestedContainer.encode(surname, forKey: .surname)
17+
}
18+
19+
private enum NameCodingKeys: String, CodingKey {
20+
case firstName = "firstName"
21+
case surname = "surname"
22+
}
23+
}
24+
25+
do {
26+
let object = ObjectInArray(firstName: "Adam", surname: "Fowler")
27+
let json = try PureSwiftJSONCoding.JSONEncoder().encode(object)
28+
29+
let parsed = try JSONParser().parse(bytes: json)
30+
XCTAssertEqual(parsed, .array([.object(["firstName": .string("Adam"), "surname": .string("Fowler")])]))
31+
}
32+
catch {
33+
XCTFail("Unexpected error: \(error)")
34+
}
35+
}
36+
37+
func testNestedUnkeyedContainer() {
38+
struct NumbersInArray: Encodable {
39+
let numbers: [Int]
40+
41+
func encode(to encoder: Encoder) throws {
42+
var container = encoder.unkeyedContainer()
43+
var numbersContainer = container.nestedUnkeyedContainer()
44+
try numbers.forEach() { try numbersContainer.encode($0) }
45+
}
46+
47+
private enum CodingKeys: String, CodingKey {
48+
case numbers
49+
}
50+
}
51+
52+
do {
53+
let object = NumbersInArray(numbers: [1, 2, 3, 4])
54+
let json = try PureSwiftJSONCoding.JSONEncoder().encode(object)
55+
56+
let parsed = try JSONParser().parse(bytes: json)
57+
XCTAssertEqual(parsed, .array([.array([.number("1"), .number("2"), .number("3"), .number("4")])]))
58+
}
59+
catch {
60+
XCTFail("Unexpected error: \(error)")
61+
}
62+
}
63+
}
64+
65+
66+

0 commit comments

Comments
 (0)