Skip to content

Commit ba15212

Browse files
Nadohsmatthewcheok
authored andcommitted
Mixed array (matthewcheok#59)
* add `filter` option on JSONDecodable `decode` methods that return array types add `filter` option to array initializer - these options allow mapping over JSON array content to no longer fail when JSON objects in array are not all of same kind. * fix warnings * fix file merge
1 parent eaba37b commit ba15212

File tree

6 files changed

+161
-25
lines changed

6 files changed

+161
-25
lines changed

JSONCodable.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
520B867D1DCEA60900885504 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520B867C1DCEA60900885504 /* ArrayTests.swift */; };
11+
520B867F1DCEB6A300885504 /* PropertyCompany.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520B867E1DCEB6A300885504 /* PropertyCompany.swift */; };
1012
5211CD0A1CE2EBFB0097F255 /* NestItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5211CD091CE2EBFB0097F255 /* NestItem.swift */; };
1113
52E8F44F1C9087D200F40F7F /* UtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E8F44E1C9087D200F40F7F /* UtilityTests.swift */; };
1214
9E455BFA1BCE185B00070A4F /* EnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E455BF91BCE185B00070A4F /* EnumTests.swift */; };
@@ -53,6 +55,8 @@
5355
/* End PBXContainerItemProxy section */
5456

5557
/* Begin PBXFileReference section */
58+
520B867C1DCEA60900885504 /* ArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = "<group>"; };
59+
520B867E1DCEB6A300885504 /* PropertyCompany.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyCompany.swift; sourceTree = "<group>"; };
5660
5211CD091CE2EBFB0097F255 /* NestItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NestItem.swift; sourceTree = "<group>"; };
5761
52E8F44E1C9087D200F40F7F /* UtilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityTests.swift; sourceTree = "<group>"; };
5862
9E455BF71BCE185B00070A4F /* JSONCodableTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JSONCodableTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -124,6 +128,7 @@
124128
isa = PBXGroup;
125129
children = (
126130
5211CD091CE2EBFB0097F255 /* NestItem.swift */,
131+
520B867E1DCEB6A300885504 /* PropertyCompany.swift */,
127132
BDD667CB1D1F3572003F94D7 /* Messages.swift */,
128133
BD885BBF1D173A0700CA767A /* PropertyItem.swift */,
129134
9E455C021BCE1C1E00070A4F /* Fruit.swift */,
@@ -140,6 +145,7 @@
140145
isa = PBXGroup;
141146
children = (
142147
9ECF00C31BCF82F5008D557C /* HelperTests.swift */,
148+
520B867C1DCEA60900885504 /* ArrayTests.swift */,
143149
52E8F44E1C9087D200F40F7F /* UtilityTests.swift */,
144150
9E455C0A1BCE1F0100070A4F /* RegularTests.swift */,
145151
BD885BBD1D17358E00CA767A /* EncodeNestingTests.swift */,
@@ -343,11 +349,13 @@
343349
A1B71C7E1D37E90B006DA33A /* MirrorTests.swift in Sources */,
344350
9ECF00C41BCF82F5008D557C /* HelperTests.swift in Sources */,
345351
9ECF00C21BCF6E43008D557C /* ImageAsset.swift in Sources */,
352+
520B867D1DCEA60900885504 /* ArrayTests.swift in Sources */,
346353
9E455C031BCE1C1E00070A4F /* Fruit.swift in Sources */,
347354
9ECF00C01BCE251B008D557C /* TransformerTests.swift in Sources */,
348355
BDD667CC1D1F3572003F94D7 /* Messages.swift in Sources */,
349356
9E455BFA1BCE185B00070A4F /* EnumTests.swift in Sources */,
350357
9E8E07241BD3F15800F98421 /* Food.swift in Sources */,
358+
520B867F1DCEB6A300885504 /* PropertyCompany.swift in Sources */,
351359
BD885BBE1D17358E00CA767A /* EncodeNestingTests.swift in Sources */,
352360
9E455C0B1BCE1F0100070A4F /* RegularTests.swift in Sources */,
353361
9E455C051BCE1D0700070A4F /* User.swift in Sources */,

JSONCodable/JSONDecodable.swift

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,20 @@ public extension JSONDecodable {
6969
}
7070

7171
public extension Array where Element: JSONDecodable {
72-
init(JSONArray: [Any]) throws {
72+
init(JSONArray: [Any], filtered: Bool = false) throws {
7373
self.init(try JSONArray.flatMap {
7474
guard let json = $0 as? [String : Any] else {
7575
throw JSONDecodableError.dictionaryTypeExpectedError(key: "n/a", elementType: type(of: $0))
7676
}
77-
return try Element(object: json)
78-
})
77+
if filtered {
78+
return try? Element(object: json)
79+
} else {
80+
return try Element(object: json)
81+
}
82+
})
7983
}
84+
85+
8086
}
8187

8288
// JSONDecoder - provides utility methods for decoding
@@ -136,12 +142,12 @@ public class JSONDecoder {
136142
}
137143
return compatible
138144
}
139-
145+
140146
// JSONCompatible?
141147
public func decode<Compatible: JSONCompatible>(_ key: String) throws -> Compatible? {
142148
return (get(key) ?? object[key] as Any) as? Compatible
143149
}
144-
150+
145151
// JSONDecodable
146152
public func decode<Decodable: JSONDecodable>(_ key: String) throws -> Decodable {
147153
guard let value = get(key) else {
@@ -193,7 +199,7 @@ public class JSONDecoder {
193199
}
194200

195201
// [JSONCompatible]
196-
public func decode<Element: JSONCompatible>(_ key: String) throws -> [Element] {
202+
public func decode<Element: JSONCompatible>(_ key: String, filter: Bool = false) throws -> [Element] {
197203
guard let value = get(key) else {
198204
return []
199205
}
@@ -215,29 +221,41 @@ public class JSONDecoder {
215221
}
216222

217223
// [JSONDecodable]
218-
public func decode<Element: JSONDecodable>(_ key: String) throws -> [Element] {
224+
public func decode<Element: JSONDecodable>(_ key: String, filter: Bool = false) throws -> [Element] {
219225
guard let value = get(key) else {
220226
return []
221227
}
222228
guard let array = value as? [JSONObject] else {
223229
throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value))
224230
}
225-
return try array.flatMap { try Element(object: $0)}
231+
return try array.flatMap {
232+
if filter {
233+
return try? Element(object: $0)
234+
} else {
235+
return try Element(object: $0)
236+
}
237+
}
226238
}
227239

228240
// [JSONDecodable]?
229-
public func decode<Element: JSONDecodable>(_ key: String) throws -> [Element]? {
241+
public func decode<Element: JSONDecodable>(_ key: String, filter: Bool = false) throws -> [Element]? {
230242
guard let value = get(key) else {
231243
return nil
232244
}
233245
guard let array = value as? [JSONObject] else {
234246
throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value))
235247
}
236-
return try array.flatMap { try Element(object: $0)}
248+
return try array.flatMap {
249+
if filter {
250+
return try? Element(object: $0)
251+
} else {
252+
return try Element(object: $0)
253+
}
254+
}
237255
}
238-
256+
239257
// [[JSONDecodable]]
240-
public func decode<Element: JSONDecodable>(_ key: String) throws -> [[Element]] {
258+
public func decode<Element: JSONDecodable>(_ key: String, filter: Bool = false) throws -> [[Element]] {
241259
guard let value = get(key) else {
242260
return []
243261
}
@@ -247,8 +265,13 @@ public class JSONDecoder {
247265
var res:[[Element]] = []
248266

249267
for x in array {
250-
let nested = try x.flatMap { try Element(object: $0)}
251-
res.append(nested)
268+
if filter {
269+
let nested = x.flatMap { try? Element(object: $0)}
270+
res.append(nested)
271+
} else {
272+
let nested = try x.flatMap { try Element(object: $0)}
273+
res.append(nested)
274+
}
252275
}
253276
return res
254277
}

JSONCodableTests/ArrayTests.swift

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// DictionaryTests.swift
3+
// JSONCodable
4+
//
5+
// Created by FoxRichard on 11/5/16.
6+
//
7+
//
8+
9+
import XCTest
10+
11+
class ArrayTests: XCTestCase {
12+
13+
let mixedArrayJSON = [
14+
[
15+
"class": "propertyType",
16+
"rel": "propertyType",
17+
"properties":
18+
[ "name": "John",
19+
"location": [ "coord": [
20+
"lat": 37.790770,
21+
"long": -122.402015
22+
]]]],
23+
["name": "CompanyInc",
24+
"address": "1414 place st Los Angeles, CA"],
25+
[
26+
"class": "propertyType",
27+
"rel": "propertyType",
28+
"properties":
29+
[ "name": "Joe",
30+
"location": [ "coord": [
31+
"lat": 38.790770,
32+
"long": -121.402015
33+
]]]],
34+
["name": "SoftwareInc",
35+
"address": "1313 place st Oakland, CA"]
36+
]
37+
38+
let companiesJSON: [[String: String]] = [
39+
["name": "CompanyInc",
40+
"address": "1414 place st Los Angeles, CA"],
41+
["name": "SoftwareInc",
42+
"address": "1313 place st Oakland, CA"]
43+
]
44+
45+
func testMixedItemsInArray() {
46+
do {
47+
let companies = try [Company](JSONArray: mixedArrayJSON, filtered: true)
48+
guard let companyValues = try? companies.toJSON(),
49+
let companiesEncoded:[[String: String]] = (companyValues as? [[String: String]]) else {
50+
XCTFail()
51+
return
52+
}
53+
XCTAssert(companiesEncoded.count == 2, "encoding invalid")
54+
XCTAssert(companiesJSON.count == 2, "companies mapping invalid")
55+
XCTAssert(companiesEncoded[0] == companiesJSON[0], "companies values incorrect")
56+
XCTAssert(companiesEncoded[1] == companiesJSON[1], "companies values incorrect")
57+
print(companies)
58+
} catch {
59+
print("\(error)")
60+
XCTFail()
61+
}
62+
}
63+
64+
func testMixedItemsInArrayNotFiltered() {
65+
do {
66+
let _ = try [Company](JSONArray: mixedArrayJSON, filtered: false)
67+
XCTFail()
68+
} catch {
69+
print("mapping should fail if not filtered")
70+
}
71+
}
72+
73+
func testCompanyProperties() {
74+
let companyPropertiesJSON = ["companies_properties" : mixedArrayJSON]
75+
do {
76+
let companiesAndProperties = try PropertyCompany(object: companyPropertiesJSON)
77+
print(companiesAndProperties)
78+
} catch {
79+
print(error)
80+
XCTFail()
81+
}
82+
83+
84+
}
85+
}

JSONCodableTests/EnumTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class EnumTests: XCTestCase {
1919
func testDecodingEnum() {
2020
guard let fruit = try? Fruit(object: encodedValue) else {
2121
XCTFail()
22-
return
22+
return
2323
}
2424

2525
XCTAssertEqual(fruit, decodedValue)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// PropertyCompany.swift
3+
// JSONCodable
4+
//
5+
// Created by FoxRichard on 11/5/16.
6+
//
7+
//
8+
9+
import JSONCodable
10+
11+
struct PropertyCompany {
12+
let properties: [PropertyItem]
13+
let companies: [Company]
14+
}
15+
16+
extension PropertyCompany: JSONEncodable {}
17+
18+
extension PropertyCompany: JSONDecodable {
19+
init(object: JSONObject) throws {
20+
let decoder = JSONDecoder(object: object)
21+
properties = try decoder.decode("companies_properties", filter: true)
22+
companies = try decoder.decode("companies_properties", filter: true)
23+
}
24+
}

JSONCodableTests/PropertyItem.swift

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,12 @@ struct PropertyItem {
1818

1919
extension PropertyItem: JSONDecodable {
2020
init(object: JSONObject) throws {
21-
do {
22-
let decoder = JSONDecoder(object: object)
23-
rel = try decoder.decode("rel")
24-
type = try decoder.decode("class")
25-
name = try decoder.decode("properties.name")
26-
long = try decoder.decode("properties.location.coord.long")
27-
lat = try decoder.decode("properties.location.coord.lat")
28-
}catch{
29-
fatalError("\(error)")
30-
}
21+
let decoder = JSONDecoder(object: object)
22+
rel = try decoder.decode("rel")
23+
type = try decoder.decode("class")
24+
name = try decoder.decode("properties.name")
25+
long = try decoder.decode("properties.location.coord.long")
26+
lat = try decoder.decode("properties.location.coord.lat")
3127
}
3228
}
3329

0 commit comments

Comments
 (0)