Skip to content

Commit 738726f

Browse files
author
mdmathias
authored
Merge branch 'master' into mattmatt/decode-null-into-nil
2 parents 4b243f0 + c630c95 commit 738726f

9 files changed

+241
-29
lines changed

Configurations/Base.xcconfig

+2
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ ENABLE_BITCODE[sdk=macosx*] = NO
4242
CODE_SIGN_IDENTITY[sdk=macosx*] = -
4343
CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
4444
CODE_SIGN_IDENTITY[sdk=appletvos*] = iPhone Developer
45+
46+
SWIFT_VERSION = 2.3

Configurations/Release.xcconfig

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ VALIDATE_PRODUCT = YES
77
GCC_OPTIMIZATION_LEVEL = s
88
ENABLE_NS_ASSERTIONS = NO
99
GCC_PREPROCESSOR_DEFINITIONS =
10-
SWIFT_OPTIMIZATION_LEVEL = -O
10+
SWIFT_OPTIMIZATION_LEVEL = -Owholemodule
1111
COPY_PHASE_STRIP = YES

Freddy.xcodeproj/project.pbxproj

+2
Original file line numberDiff line numberDiff line change
@@ -462,9 +462,11 @@
462462
};
463463
DB6ADF3D1C23612000D77BF1 = {
464464
CreatedOnToolsVersion = 7.2;
465+
LastSwiftMigration = 0800;
465466
};
466467
DB6ADF461C23612000D77BF1 = {
467468
CreatedOnToolsVersion = 7.2;
469+
LastSwiftMigration = 0800;
468470
};
469471
DB6ADF591C23612900D77BF1 = {
470472
CreatedOnToolsVersion = 7.2;

Sources/JSONDecodable.swift

+32-6
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,19 @@ extension String: JSONDecodable {
6767
/// an instance of `String` cannot be created from the `JSON` value that was
6868
/// passed to this initializer.
6969
public init(json: JSON) throws {
70-
guard case let .String(string) = json else {
70+
71+
switch json {
72+
case let .String(string):
73+
self = string
74+
case let .Int(int):
75+
self = String(int)
76+
case let .Bool(bool):
77+
self = String(bool)
78+
case let .Double(double):
79+
self = String(double)
80+
default:
7181
throw JSON.Error.ValueNotConvertible(value: json, to: Swift.String)
7282
}
73-
self = string
7483
}
7584

7685
}
@@ -110,6 +119,7 @@ extension RawRepresentable where RawValue: JSONDecodable {
110119
internal extension JSON {
111120

112121
/// Retrieves a `[JSON]` from the JSON.
122+
/// - parameter: A `JSON` to be used to create the returned `Array`.
113123
/// - returns: An `Array` of `JSON` elements
114124
/// - throws: Any of the `JSON.Error` cases thrown by `decode(type:)`.
115125
/// - seealso: `JSON.decode(_:type:)`
@@ -122,6 +132,7 @@ internal extension JSON {
122132
}
123133

124134
/// Retrieves a `[String: JSON]` from the JSON.
135+
/// - parameter: A `JSON` to be used to create the returned `Dictionary`.
125136
/// - returns: An `Dictionary` of `String` mapping to `JSON` elements
126137
/// - throws: Any of the `JSON.Error` cases thrown by `decode(type:)`.
127138
/// - seealso: `JSON.decode(_:type:)`
@@ -135,10 +146,8 @@ internal extension JSON {
135146

136147
/// Attempts to decode many values from a descendant JSON array at a path
137148
/// into JSON.
138-
/// - parameter type: If the context this method is called from does not
139-
/// make the return type clear, pass a type implementing `JSONDecodable`
140-
/// to disambiguate the type to decode with.
141-
/// - returns: An `Array` of decoded elements
149+
/// - parameter: A `JSON` to be used to create the returned `Array` of some type conforming to `JSONDecodable`.
150+
/// - returns: An `Array` of `Decoded` elements.
142151
/// - throws: Any of the `JSON.Error` cases thrown by `decode(type:)`, as
143152
/// well as any error that arises from decoding the contained values.
144153
/// - seealso: `JSON.decode(_:type:)`
@@ -148,4 +157,21 @@ internal extension JSON {
148157
return try getArray(json).map(Decoded.init)
149158
}
150159

160+
/// Attempts to decode many values from a descendant JSON object at a path
161+
/// into JSON.
162+
/// - returns: A `Dictionary` of string keys and `Decoded` values.
163+
/// - throws: One of the `JSON.Error` cases thrown by `decode(_:type:)` or
164+
/// any error that arises from decoding the contained values.
165+
/// - seealso: `JSON.decode(_:type:)`
166+
static func getDictionaryOf<Decoded: JSONDecodable>(json: JSON) throws -> [Swift.String: Decoded] {
167+
guard case let .Dictionary(dictionary) = json else {
168+
throw Error.ValueNotConvertible(value: json, to: Swift.Dictionary<Swift.String, Decoded>)
169+
}
170+
var decodedDictionary = Swift.Dictionary<Swift.String, Decoded>(minimumCapacity: dictionary.count)
171+
for (key, value) in dictionary {
172+
decodedDictionary[key] = try Decoded(json: value)
173+
}
174+
return decodedDictionary
175+
}
176+
151177
}

Sources/JSONParser.swift

+14-6
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,12 @@ public struct JSONParser {
158158
return try decodeNumberAsString(start)
159159
}
160160
}
161-
162-
throw Error.ValueInvalid(offset: loc, character: UnicodeScalar(input[loc]))
161+
162+
if loc < input.count {
163+
throw Error.ValueInvalid(offset: loc, character: UnicodeScalar(input[loc]))
164+
} else {
165+
throw Error.EndOfStreamUnexpected
166+
}
163167
}
164168

165169
private mutating func skipWhitespace() {
@@ -659,12 +663,16 @@ private struct NumberParser {
659663
return
660664
}
661665

662-
guard input[loc] == Literal.PERIOD else {
666+
switch input[loc] {
667+
case Literal.PERIOD:
668+
state = .Decimal
669+
670+
case Literal.e, Literal.E:
671+
state = .Exponent
672+
673+
default:
663674
state = .Done
664-
return
665675
}
666-
667-
state = .Decimal
668676
}
669677

670678
mutating func parsePreDecimalDigits(@noescape f: (UInt8) throws -> Void) rethrows {

Sources/JSONSubscripting.swift

+70-8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ extension JSONPathType {
4949
extension String: JSONPathType {
5050

5151
/// A method used to retrieve a value from a given dictionary for a specific key.
52+
/// - parameter dictionary: A `Dictionary` with `String` keys and `JSON` values.
5253
/// - throws: `.KeyNotFound` with an associated value of `self`, where `self` is a `String`,
5354
/// should the key not be present within the `JSON`.
5455
/// - returns: The `JSON` value associated with the given key.
@@ -64,6 +65,7 @@ extension String: JSONPathType {
6465
extension Int: JSONPathType {
6566

6667
/// A method used to retrieve a value from a given array for a specific index.
68+
/// - parameter array: An `Array` of `JSON`.
6769
/// - throws: `.IndexOutOfBounds` with an associated value of `self`, where `self` is an `Int`,
6870
/// should the index not be within the valid range for the array of `JSON`.
6971
/// - returns: The `JSON` value found at the given index.
@@ -189,7 +191,7 @@ extension JSON {
189191
return try JSON.getArray(valueAtPath(path))
190192
}
191193

192-
/// Attempts to decodes many values from a desendant JSON array at a path
194+
/// Attempts to decode many values from a descendant JSON array at a path
193195
/// into JSON.
194196
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
195197
/// - parameter type: If the context this method is called from does not
@@ -211,6 +213,20 @@ extension JSON {
211213
public func dictionary(path: JSONPathType...) throws -> [Swift.String: JSON] {
212214
return try JSON.getDictionary(valueAtPath(path))
213215
}
216+
217+
/// Attempts to decode many values from a descendant JSON object at a path
218+
/// into JSON.
219+
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
220+
/// - parameter type: If the context this method is called from does not
221+
/// make the return type clear, pass a type implementing `JSONDecodable`
222+
/// to disambiguate the value type to decode with.
223+
/// - returns: A `Dictionary` of `String` keys and decoded values.
224+
/// - throws: One of the `JSON.Error` cases thrown by `decode(_:type:)` or
225+
/// any error that arises from decoding the contained values.
226+
/// - seealso: `JSON.decode(_:type:)`
227+
public func dictionaryOf<Decoded: JSONDecodable>(path: JSONPathType..., type: Decoded.Type = Decoded.self) throws -> [Swift.String: Decoded] {
228+
return try JSON.getDictionaryOf(valueAtPath(path))
229+
}
214230

215231
}
216232

@@ -376,6 +392,9 @@ extension JSON {
376392
/// JSON.
377393
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
378394
/// - parameter alongPath: Options that control what should be done with values that are `null` or keys that are missing.
395+
/// - parameter type: If the context this method is called from does not
396+
/// make the return type clear, pass a type implementing `JSONDecodable`
397+
/// to disambiguate the value type to decode with.
379398
/// - returns: An `Array` of decoded elements if found, otherwise `nil`.
380399
/// - throws: One of the following errors contained in `JSON.Error`:
381400
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
@@ -409,6 +428,29 @@ extension JSON {
409428
public func dictionary(path: JSONPathType..., alongPath options: SubscriptingOptions) throws -> [Swift.String: JSON]? {
410429
return try mapOptionalAtPath(path, alongPath: options, transform: JSON.getDictionary)
411430
}
431+
432+
/// Optionally attempts to decode many values from a descendant object at a path
433+
/// into JSON.
434+
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
435+
/// - parameter alongPath: Options that control what should be done with values that are `null` or keys that are missing.
436+
/// - parameter type: If the context this method is called from does not
437+
/// make the return type clear, pass a type implementing `JSONDecodable`
438+
/// to disambiguate the value type to decode with.
439+
/// - returns: A `Dictionary` of `String` mapping to decoded elements if a
440+
/// value could be found, otherwise `nil`.
441+
/// - throws: One of the following errors contained in `JSON.Error`:
442+
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
443+
/// `JSON` dictionary.
444+
/// * `IndexOutOfBounds`: An index `path` is outside the bounds of a
445+
/// descendant `JSON` array.
446+
/// * `UnexpectedSubscript`: A `path` item cannot be used with the
447+
/// corresponding `JSON` value.
448+
/// * `TypeNotConvertible`: The target value's type inside of the `JSON`
449+
/// instance does not match the decoded value.
450+
/// * Any error that arises from decoding the value.
451+
public func dictionaryOf<Decoded: JSONDecodable>(path: JSONPathType..., alongPath options: SubscriptingOptions, type: Decoded.Type = Decoded.self) throws -> [Swift.String: Decoded]? {
452+
return try mapOptionalAtPath(path, alongPath: options, transform: JSON.getDictionaryOf)
453+
}
412454

413455
}
414456

@@ -436,7 +478,7 @@ extension JSON {
436478

437479
/// Retrieves a `Double` from a path into JSON or a fallback if not found.
438480
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
439-
/// - parameter fallback: Array to use when one is missing at the subscript.
481+
/// - parameter fallback: `Double` to use when one is missing at the subscript.
440482
/// - returns: A floating-point `Double`
441483
/// - throws: One of the `JSON.Error` cases thrown by calling `mapOptionalAtPath(_:fallback:transform:)`.
442484
/// - seealso: `optionalAtPath(_:ifNotFound)`.
@@ -446,7 +488,7 @@ extension JSON {
446488

447489
/// Retrieves an `Int` from a path into JSON or a fallback if not found.
448490
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
449-
/// - parameter fallback: Array to use when one is missing at the subscript.
491+
/// - parameter fallback: `Int` to use when one is missing at the subscript.
450492
/// - returns: A numeric `Int`
451493
/// - throws: One of the following errors contained in `JSON.Error`:
452494
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
@@ -463,7 +505,7 @@ extension JSON {
463505

464506
/// Retrieves a `String` from a path into JSON or a fallback if not found.
465507
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
466-
/// - parameter fallback: Array to use when one is missing at the subscript.
508+
/// - parameter fallback: `String` to use when one is missing at the subscript.
467509
/// - returns: A textual `String`
468510
/// - throws: One of the following errors contained in `JSON.Error`:
469511
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
@@ -480,7 +522,7 @@ extension JSON {
480522

481523
/// Retrieves a `Bool` from a path into JSON or a fallback if not found.
482524
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
483-
/// - parameter fallback: Array to use when one is missing at the subscript.
525+
/// - parameter fallback: `Bool` to use when one is missing at the subscript.
484526
/// - returns: A truthy `Bool`
485527
/// - throws: One of the following errors contained in `JSON.Error`:
486528
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
@@ -497,7 +539,7 @@ extension JSON {
497539

498540
/// Retrieves a `[JSON]` from a path into JSON or a fallback if not found.
499541
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
500-
/// - parameter fallback: Array to use when one is missing at the subscript.
542+
/// - parameter fallback: `Array` to use when one is missing at the subscript.
501543
/// - returns: An `Array` of `JSON` elements
502544
/// - throws: One of the following errors contained in `JSON.Error`:
503545
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
@@ -515,7 +557,7 @@ extension JSON {
515557
/// Attempts to decodes many values from a desendant JSON array at a path
516558
/// into the recieving structure, returning a fallback if not found.
517559
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
518-
/// - parameter fallback: Array to use when one is missing at the subscript.
560+
/// - parameter fallback: `Array` to use when one is missing at the subscript.
519561
/// - returns: An `Array` of decoded elements
520562
/// - throws: One of the following errors contained in `JSON.Error`:
521563
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
@@ -534,7 +576,7 @@ extension JSON {
534576
/// Retrieves a `[String: JSON]` from a path into JSON or a fallback if not
535577
/// found.
536578
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
537-
/// - parameter fallback: Value to use when one is missing at the subscript
579+
/// - parameter fallback: `Dictionary` to use when one is missing at the subscript.
538580
/// - returns: An `Dictionary` of `String` mapping to `JSON` elements
539581
/// - throws: One of the following errors contained in `JSON.Error`:
540582
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
@@ -549,6 +591,26 @@ extension JSON {
549591
return try mapOptionalAtPath(path, fallback: fallback, transform: JSON.getDictionary)
550592
}
551593

594+
/// Attempts to decode many values from a descendant JSON object at a path
595+
/// into the receiving structure, returning a fallback if not found.
596+
/// - parameter path: 0 or more `String` or `Int` that subscript the `JSON`
597+
/// - parameter fallback: Value to use when one is missing at the subscript
598+
/// - returns: A `Dictionary` of `String` mapping to decoded elements.
599+
/// - throws: One of the following errors contained in `JSON.Error`:
600+
/// * `KeyNotFound`: A key `path` does not exist inside a descendant
601+
/// `JSON` dictionary.
602+
/// * `IndexOutOfBounds`: An index `path` is outside the bounds of a
603+
/// descendant `JSON` array.
604+
/// * `UnexpectedSubscript`: A `path` item cannot be used with the
605+
/// corresponding `JSON` value.
606+
/// * `TypeNotConvertible`: The target value's type inside of the `JSON`
607+
/// instance does not match the decoded value.
608+
/// * Any error that arises from decoding the value.
609+
public func dictionaryOf<Decoded: JSONDecodable>(path: JSONPathType..., @autoclosure or fallback: () -> [Swift.String: Decoded]) throws -> [Swift.String: Decoded] {
610+
return try mapOptionalAtPath(path, fallback: fallback, transform: JSON.getDictionaryOf)
611+
}
612+
613+
552614
}
553615

554616
// MARK: - Deprecated methods

Tests/JSONDecodableTests.swift

+32-3
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,28 @@ class JSONDecodableTests: XCTestCase {
9595
}
9696

9797
do {
98-
_ = try String(json: 4)
99-
XCTFail("Should not be able to instantiate `String` with `Int` `JSON`.")
98+
let four = try String(json: 4)
99+
XCTAssertEqual(four, "4", "`four` and `4` should be equal.")
100100
} catch JSON.Error.ValueNotConvertible(let type) {
101-
XCTAssert(true, "\(type) should not be covertible from 'bad' `Int.")
101+
XCTAssert(true, "\(type) should be covertible from `Int.")
102+
} catch {
103+
XCTFail("Failed for unknown reason: \(error).")
104+
}
105+
106+
do {
107+
let twoAndHalf = try String(json: 2.5)
108+
XCTAssertEqual(twoAndHalf, "2.5", "`twoAndHalf` and `2.5` should be equal.")
109+
} catch JSON.Error.ValueNotConvertible(let type) {
110+
XCTAssert(true, "\(type) should be covertible from `Double.")
111+
} catch {
112+
XCTFail("Failed for unknown reason: \(error).")
113+
}
114+
115+
do {
116+
let positive = try String(json: true)
117+
XCTAssertEqual(positive, "true", "`positive` and `true` should be equal.")
118+
} catch JSON.Error.ValueNotConvertible(let type) {
119+
XCTAssert(true, "\(type) should be covertible from `Bool.")
102120
} catch {
103121
XCTFail("Failed for unknown reason: \(error).")
104122
}
@@ -188,6 +206,17 @@ class JSONDecodableTests: XCTestCase {
188206
}
189207
}
190208

209+
func testThatDictionaryOfCanReturnDictionaryOfJSONDecodable() {
210+
let oneTwoThreeJSON: JSON = ["one": 1, "two": 2, "three": 3]
211+
212+
do {
213+
let decodedOneTwoThree = try oneTwoThreeJSON.dictionaryOf(type: Swift.Int)
214+
XCTAssertEqual(decodedOneTwoThree, ["one": 1, "two": 2, "three": 3], "`decodedOneTwoThree` should be equal to `[\"one\": 1, \"two\": 2, \"three\": 3]`.")
215+
} catch {
216+
XCTFail("`decodedOneTwoThree` should be equal to `[\"one\": 1, \"two\": 2, \"three\": 3]`.")
217+
}
218+
}
219+
191220
func testThatNullIsDecodedToNilWhenRequestedAtTopLevel() {
192221
let JSONDictionary: JSON = ["key": .Null]
193222

0 commit comments

Comments
 (0)