Skip to content

Commit 7c3ce43

Browse files
✨ better support for Int-based dictionaries
Signed-off-by: Florian Krüger <[email protected]>
1 parent 55e155c commit 7c3ce43

File tree

7 files changed

+262
-29
lines changed

7 files changed

+262
-29
lines changed

JSONCodable.xcodeproj/project.pbxproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
A1B71C7E1D37E90B006DA33A /* MirrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B71C7D1D37E90B006DA33A /* MirrorTests.swift */; };
3333
A1B71C801D37E982006DA33A /* ClassInheritance.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B71C7F1D37E982006DA33A /* ClassInheritance.swift */; };
3434
A5481F9E22D637B600D8DF85 /* IntBasedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5481F9D22D637B600D8DF85 /* IntBasedTests.swift */; };
35-
A5481FA022D6386D00D8DF85 /* Companies.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5481F9F22D6386D00D8DF85 /* Companies.swift */; };
35+
A5481FA022D6386D00D8DF85 /* IntBasedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5481F9F22D6386D00D8DF85 /* IntBasedModel.swift */; };
36+
A5481FA722D71BFE00D8DF85 /* ComplexIntBasedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5481FA622D71BFE00D8DF85 /* ComplexIntBasedModel.swift */; };
3637
BD885BBE1D17358E00CA767A /* EncodeNestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD885BBD1D17358E00CA767A /* EncodeNestingTests.swift */; };
3738
BD885BC01D173A0700CA767A /* PropertyItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD885BBF1D173A0700CA767A /* PropertyItem.swift */; };
3839
BDD667CC1D1F3572003F94D7 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDD667CB1D1F3572003F94D7 /* Messages.swift */; };
@@ -78,7 +79,8 @@
7879
A1B71C7D1D37E90B006DA33A /* MirrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MirrorTests.swift; sourceTree = "<group>"; };
7980
A1B71C7F1D37E982006DA33A /* ClassInheritance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassInheritance.swift; sourceTree = "<group>"; };
8081
A5481F9D22D637B600D8DF85 /* IntBasedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntBasedTests.swift; sourceTree = "<group>"; };
81-
A5481F9F22D6386D00D8DF85 /* Companies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Companies.swift; sourceTree = "<group>"; };
82+
A5481F9F22D6386D00D8DF85 /* IntBasedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntBasedModel.swift; sourceTree = "<group>"; };
83+
A5481FA622D71BFE00D8DF85 /* ComplexIntBasedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplexIntBasedModel.swift; sourceTree = "<group>"; };
8284
BD885BBD1D17358E00CA767A /* EncodeNestingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncodeNestingTests.swift; sourceTree = "<group>"; };
8385
BD885BBF1D173A0700CA767A /* PropertyItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyItem.swift; sourceTree = "<group>"; };
8486
BDD667CB1D1F3572003F94D7 /* Messages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = "<group>"; };
@@ -125,7 +127,8 @@
125127
9E455C081BCE1DE100070A4F /* Company.swift */,
126128
9ECF00C11BCF6E43008D557C /* ImageAsset.swift */,
127129
A1B71C7F1D37E982006DA33A /* ClassInheritance.swift */,
128-
A5481F9F22D6386D00D8DF85 /* Companies.swift */,
130+
A5481F9F22D6386D00D8DF85 /* IntBasedModel.swift */,
131+
A5481FA622D71BFE00D8DF85 /* ComplexIntBasedModel.swift */,
129132
);
130133
name = Models;
131134
sourceTree = "<group>";
@@ -300,7 +303,7 @@
300303
files = (
301304
52E8F44F1C9087D200F40F7F /* UtilityTests.swift in Sources */,
302305
A1B71C801D37E982006DA33A /* ClassInheritance.swift in Sources */,
303-
A5481FA022D6386D00D8DF85 /* Companies.swift in Sources */,
306+
A5481FA022D6386D00D8DF85 /* IntBasedModel.swift in Sources */,
304307
5211CD0A1CE2EBFB0097F255 /* NestItem.swift in Sources */,
305308
A5481F9E22D637B600D8DF85 /* IntBasedTests.swift in Sources */,
306309
A1B71C7E1D37E90B006DA33A /* MirrorTests.swift in Sources */,
@@ -318,6 +321,7 @@
318321
9E455C051BCE1D0700070A4F /* User.swift in Sources */,
319322
BD885BC01D173A0700CA767A /* PropertyItem.swift in Sources */,
320323
9E455C091BCE1DE100070A4F /* Company.swift in Sources */,
324+
A5481FA722D71BFE00D8DF85 /* ComplexIntBasedModel.swift in Sources */,
321325
A10DFC4C1DF71BF400B7D6D7 /* ClassInheritanceTests.swift in Sources */,
322326
);
323327
runOnlyForDeploymentPostprocessing = 0;

JSONCodable/JSONDecodable.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,40 @@ public class JSONDecoder {
419419
return decoded
420420
}
421421

422+
// [Int:[JSONDecodable]]
423+
public func decode<Element: JSONDecodable>(_ key: String) throws -> [Int: [Element]] {
424+
guard let value = get(key) else {
425+
throw JSONDecodableError.missingTypeError(key: key)
426+
}
427+
guard let dictionary = value as? [Int: [JSONObject]] else {
428+
throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [Int: Element].self)
429+
}
430+
var decoded = [Int: [Element]]()
431+
try dictionary.forEach {
432+
decoded[$0] = try $1.map { element in
433+
return try Element(object: element)
434+
}
435+
}
436+
return decoded
437+
}
438+
439+
// [Int:[JSONDecodable]]?
440+
public func decode<Element: JSONDecodable>(_ key: String) throws -> [Int: [Element]]? {
441+
guard let value = get(key) else {
442+
return nil
443+
}
444+
guard let dictionary = value as? [Int: [JSONObject]] else {
445+
throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [Int: Element].self)
446+
}
447+
var decoded = [Int: [Element]]()
448+
try dictionary.forEach {
449+
decoded[$0] = try $1.map { element in
450+
return try Element(object: element)
451+
}
452+
}
453+
return decoded
454+
}
455+
422456
// JSONTransformable
423457
public func decode<EncodedType, DecodedType>(_ key: String, transformer: JSONTransformer<EncodedType, DecodedType>) throws -> DecodedType {
424458
guard let value = get(key) else {

JSONCodable/JSONEncodable.swift

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public extension Array { //where Element: JSONEncodable {
121121

122122
// Dictionary convenience methods
123123

124-
public extension Dictionary {//where Key: String, Value: JSONEncodable {
124+
public extension Dictionary where Key == String { //Value: JSONEncodable {
125125
func toJSON() throws -> Any {
126126
var result: [String: Any] = [:]
127127
for (k, item) in self {
@@ -136,6 +136,24 @@ public extension Dictionary {//where Key: String, Value: JSONEncodable {
136136
}
137137
}
138138

139+
public extension Dictionary where Key == Int { //Value: JSONEncodable {
140+
func toJSON() throws -> Any {
141+
var result: [Int: Any] = [:]
142+
for (k, item) in self {
143+
if let item = item as? JSONEncodable {
144+
result[k] = try item.toJSON()
145+
}
146+
else if let items = item as? [JSONEncodable] {
147+
result[k] = try items.map { try $0.toJSON() }
148+
}
149+
else {
150+
throw JSONEncodableError.dictionaryIncompatibleTypeError(elementType: type(of: item))
151+
}
152+
}
153+
return result
154+
}
155+
}
156+
139157
// JSONEncoder - provides utility methods for encoding
140158

141159
public class JSONEncoder {
@@ -291,6 +309,44 @@ public class JSONEncoder {
291309
let result = try actual.toJSON()
292310
object = update(object: object, keys: key.components(separatedBy: "."), value: result)
293311
}
312+
313+
// [Int:JSONEncodable]
314+
public func encode<Encodable: JSONEncodable>(_ dictionary: [Int:Encodable], key: String) throws {
315+
let result = try dictionary.toJSON()
316+
object = update(object: object, keys: key.components(separatedBy: "."), value: result)
317+
}
318+
public func encode(_ dictionary: [Int:JSONEncodable], key: String) throws {
319+
let result = try dictionary.toJSON()
320+
object[key] = result
321+
}
322+
323+
// [Int:JSONEncodable]?
324+
public func encode<Encodable: JSONEncodable>(_ value: [Int:Encodable]?, key: String) throws {
325+
guard let actual = value else {
326+
return
327+
}
328+
let result = try actual.toJSON()
329+
object = update(object: object, keys: key.components(separatedBy: "."), value: result)
330+
}
331+
332+
// [Int:[JSONEncodable]]
333+
public func encode<Encodable: JSONEncodable>(_ dictionary: [Int:[Encodable]], key: String) throws {
334+
let result = try dictionary.toJSON()
335+
object = update(object: object, keys: key.components(separatedBy: "."), value: result)
336+
}
337+
public func encode(_ dictionary: [Int:[JSONEncodable]], key: String) throws {
338+
let result = try dictionary.toJSON()
339+
object[key] = result
340+
}
341+
342+
// [Int:[JSONEncodable]]?
343+
public func encode<Encodable: JSONEncodable>(_ value: [Int:[Encodable]]?, key: String) throws {
344+
guard let actual = value else {
345+
return
346+
}
347+
let result = try actual.toJSON()
348+
object = update(object: object, keys: key.components(separatedBy: "."), value: result)
349+
}
294350

295351
// JSONTransformable
296352
public func encode<EncodedType, DecodedType>(_ value: DecodedType, key: String, transformer: JSONTransformer<EncodedType, DecodedType>) throws {

JSONCodableTests/Companies.swift

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// ComplexIntBasedModel.swift
3+
// JSONCodableTests
4+
//
5+
// Created by Florian Krüger on 11.07.19.
6+
//
7+
8+
import JSONCodable
9+
10+
struct ComplexIntBasedModel {
11+
let usersByLikes: [Int: [User]]
12+
}
13+
14+
extension ComplexIntBasedModel: JSONDecodable {
15+
init(object: JSONObject) throws {
16+
let decoder = JSONDecoder(object: object)
17+
usersByLikes = try decoder.decode("users_by_likes")
18+
}
19+
}
20+
21+
extension ComplexIntBasedModel: JSONEncodable {
22+
func toJSON() throws -> Any {
23+
return try JSONEncoder.create { (encoder) -> Void in
24+
try encoder.encode(usersByLikes, key: "users_by_likes")
25+
}
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// IntBasedModel.swift
3+
// JSONCodableTests
4+
//
5+
// Created by Florian Krüger on 10.07.19.
6+
//
7+
8+
import JSONCodable
9+
10+
struct IntBasedModel {
11+
let companies: [Int: Company]
12+
}
13+
14+
extension IntBasedModel: JSONDecodable {
15+
init(object: JSONObject) throws {
16+
let decoder = JSONDecoder(object: object)
17+
companies = try decoder.decode("companies")
18+
}
19+
}
20+
21+
extension IntBasedModel: JSONEncodable {
22+
func toJSON() throws -> Any {
23+
return try JSONEncoder.create { (encoder) -> Void in
24+
try encoder.encode(companies, key: "companies")
25+
}
26+
}
27+
}

JSONCodableTests/IntBasedTests.swift

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
//
77

88
import XCTest
9+
import JSONCodable
910

1011
class IntBasedTests: XCTestCase {
1112

12-
let encodedCompanies: [String: Any] = [
13+
let encodedIntBasedModel: [String: Any] = [
1314
"companies": [
1415
0: [ "name": "Apple",
1516
"address": "1 Infinite Loop, Cupertino, CA"
@@ -23,13 +24,116 @@ class IntBasedTests: XCTestCase {
2324
let decodedCompany0 = Company(name: "Apple", address: "1 Infinite Loop, Cupertino, CA")
2425
let decodedCompany1 = Company(name: "Propeller", address: "1212 broadway, Oakland, CA")
2526

26-
func testDecoding() {
27-
guard let companies = try? Companies(object: encodedCompanies) else {
27+
lazy var decodedIntBasedModel: IntBasedModel = {
28+
IntBasedModel(companies: [
29+
0: decodedCompany0,
30+
1: decodedCompany1
31+
])
32+
}()
33+
34+
let encodedComplexIntBasedModel: [String: Any] = [
35+
"users_by_likes": [
36+
1000: [
37+
["id": 27, "likes": 1000, "full_name": "Bob Jefferson", "friends" : [], ],
38+
["id": 29, "likes": 1000, "full_name": "Jen Jackson", "friends" : [], ],
39+
],
40+
9000: [
41+
["id": 36, "likes": 9000, "full_name": "Grace Ferguson", "friends" : [], ],
42+
]
43+
]
44+
]
45+
46+
let decodedUser1000_0 = User(
47+
id: 27,
48+
likes: 1000,
49+
name: "Bob Jefferson",
50+
email: nil,
51+
company: nil,
52+
friends: [],
53+
friendsLookup: nil
54+
)
55+
56+
let decodedUser1000_1 = User(
57+
id: 29,
58+
likes: 1000,
59+
name: "Jen Jackson",
60+
email: nil,
61+
company: nil,
62+
friends: [],
63+
friendsLookup: nil
64+
)
65+
66+
let decodedUser9000_0 = User(
67+
id: 36,
68+
likes: 9000,
69+
name: "Grace Ferguson",
70+
email: nil,
71+
company: nil,
72+
friends: [],
73+
friendsLookup: nil
74+
)
75+
76+
lazy var decodedComplexIntBasedModel: ComplexIntBasedModel = {
77+
ComplexIntBasedModel(usersByLikes: [
78+
1000: [decodedUser1000_0, decodedUser1000_1],
79+
9000: [decodedUser9000_0]
80+
])
81+
}()
82+
83+
func testIntBasedDecoding() {
84+
guard let decoded = try? IntBasedModel(object: encodedIntBasedModel) else {
2885
XCTFail()
2986
return
3087
}
31-
XCTAssertEqual(companies.companies[0], decodedCompany0)
32-
XCTAssertEqual(companies.companies[1], decodedCompany1)
88+
XCTAssertEqual(decoded.companies[0], decodedCompany0)
89+
XCTAssertEqual(decoded.companies[1], decodedCompany1)
90+
}
91+
92+
func testIntBasedEncoding() {
93+
do {
94+
guard
95+
let json = try decodedIntBasedModel.toJSON() as? JSONObject
96+
else { XCTFail(); return }
97+
98+
let decoded = try IntBasedModel(object: json)
99+
100+
XCTAssertEqual(decoded.companies.count, decodedIntBasedModel.companies.count)
101+
XCTAssertEqual(decoded.companies[0], decodedIntBasedModel.companies[0])
102+
XCTAssertEqual(decoded.companies[1], decodedIntBasedModel.companies[1])
103+
} catch {
104+
print("\(error.localizedDescription)")
105+
XCTFail()
106+
}
107+
}
108+
109+
func testComplexIntBasedDecoding() {
110+
guard let decoded = try? ComplexIntBasedModel(object: encodedComplexIntBasedModel) else {
111+
XCTFail()
112+
return
113+
}
114+
XCTAssertEqual(decoded.usersByLikes[1000]?[0], decodedUser1000_0)
115+
XCTAssertEqual(decoded.usersByLikes[1000]?[1], decodedUser1000_1)
116+
XCTAssertEqual(decoded.usersByLikes[9000]?[0], decodedUser9000_0)
117+
}
118+
119+
func testComplexIntBasedEncoding() {
120+
do {
121+
guard
122+
let json = try decodedComplexIntBasedModel.toJSON() as? JSONObject
123+
else { XCTFail(); return }
124+
125+
let decoded = try ComplexIntBasedModel(object: json)
126+
127+
XCTAssertEqual(decoded.usersByLikes.count, 2)
128+
XCTAssertEqual(decoded.usersByLikes[1000]?.count, 2)
129+
XCTAssertEqual(decoded.usersByLikes[1000]?[0], decodedUser1000_0)
130+
XCTAssertEqual(decoded.usersByLikes[1000]?[1], decodedUser1000_1)
131+
XCTAssertEqual(decoded.usersByLikes[9000]?.count, 1)
132+
XCTAssertEqual(decoded.usersByLikes[9000]?[0], decodedUser9000_0)
133+
} catch {
134+
print("\(error.localizedDescription)")
135+
XCTFail()
136+
}
33137
}
34138

35139
}

0 commit comments

Comments
 (0)