Skip to content

Commit

Permalink
Merge pull request #111 from ikesyo/protocol-extensions
Browse files Browse the repository at this point in the history
Add some protocol extensions for Decodable
  • Loading branch information
ikesyo committed Mar 17, 2016
2 parents 4e8d246 + 34253db commit 4357805
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 41 deletions.
15 changes: 15 additions & 0 deletions Sources/Decodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,18 @@ public protocol Decodable {
/// - Throws: DecodeError
static func decode(e: Extractor) throws -> Self
}

// MARK: - Extensions

extension Decodable {
/// - Throws: DecodeError
public static func decodeValue(JSON: AnyJSON) throws -> Self {
let extractor = Extractor(JSON)
return try self.decode(extractor)
}

/// - Throws: DecodeError
public static func decodeValue(JSON: AnyJSON, rootKeyPath: KeyPath) throws -> Self {
return try Extractor(JSON).value(rootKeyPath)
}
}
6 changes: 3 additions & 3 deletions Sources/Extractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public struct Extractor {
}

do {
return try decodeValue(rawValue)
return try T.decodeValue(rawValue)
} catch let DecodeError.MissingKeyPath(missing) {
throw DecodeError.MissingKeyPath(keyPath + missing)
} catch let DecodeError.TypeMismatch(expected, actual, _) {
Expand Down Expand Up @@ -73,7 +73,7 @@ public struct Extractor {

/// - Throws: DecodeError
public func arrayOptional<T: Decodable>(keyPath: KeyPath) throws -> [T]? {
return try _rawValue(keyPath).map(decodeArray)
return try _rawValue(keyPath).map([T].decode)
}

/// - Throws: DecodeError
Expand All @@ -87,7 +87,7 @@ public struct Extractor {

/// - Throws: DecodeError
public func dictionaryOptional<T: Decodable>(keyPath: KeyPath) throws -> [String: T]? {
return try _rawValue(keyPath).map(decodeDictionary)
return try _rawValue(keyPath).map([String: T].decode)
}
}

Expand Down
40 changes: 40 additions & 0 deletions Sources/StandardLib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ extension Bool: Decodable {
}
}

// MARK: - Extensions

extension CollectionType where Generator.Element: Decodable {
/// - Throws: DecodeError
public static func decode(JSON: AnyJSON) throws -> [Generator.Element] {
guard let array = JSON as? [AnyJSON] else {
throw typeMismatch("Array", actual: JSON, keyPath: nil)
}

return try array.map(Generator.Element.decodeValue)
}

/// - Throws: DecodeError
public static func decode(JSON: AnyJSON, rootKeyPath: KeyPath) throws -> [Generator.Element] {
return try Extractor(JSON).array(rootKeyPath)
}
}

extension DictionaryLiteralConvertible where Value: Decodable {
/// - Throws: DecodeError
public static func decode(JSON: AnyJSON) throws -> [String: Value] {
guard let dictionary = JSON as? [String: AnyJSON] else {
throw typeMismatch("Dictionary", actual: JSON, keyPath: nil)
}

var result = [String: Value](minimumCapacity: dictionary.count)
try dictionary.forEach { key, value in
result[key] = try Value.decodeValue(value)
}
return result
}

/// - Throws: DecodeError
public static func decode(JSON: AnyJSON, rootKeyPath: KeyPath) throws -> [String: Value] {
return try Extractor(JSON).dictionary(rootKeyPath)
}
}

// MARK: Helpers

internal func castOrFail<T>(e: Extractor) throws -> T {
return try castOrFail(e.rawValue)
}
Expand Down
25 changes: 6 additions & 19 deletions Sources/decode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,32 @@

/// - Throws: DecodeError
public func decodeValue<T: Decodable>(JSON: AnyJSON) throws -> T {
let extractor = Extractor(JSON)
return try T.decode(extractor)
return try T.decodeValue(JSON)
}

/// - Throws: DecodeError
public func decodeValue<T: Decodable>(JSON: AnyJSON, rootKeyPath: KeyPath) throws -> T {
return try decodeValue(JSON) <| rootKeyPath
return try T.decodeValue(JSON, rootKeyPath: rootKeyPath)
}

/// - Throws: DecodeError
public func decodeArray<T: Decodable>(JSON: AnyJSON) throws -> [T] {
guard let array = JSON as? [AnyJSON] else {
throw typeMismatch("Array", actual: JSON, keyPath: nil)
}

return try array.map(decodeValue)
return try [T].decode(JSON)
}

/// - Throws: DecodeError
public func decodeArray<T: Decodable>(JSON: AnyJSON, rootKeyPath: KeyPath) throws -> [T] {
return try decodeValue(JSON) <|| rootKeyPath
return try [T].decode(JSON, rootKeyPath: rootKeyPath)
}

/// - Throws: DecodeError
public func decodeDictionary<T: Decodable>(JSON: AnyJSON) throws -> [String: T] {
guard let dictionary = JSON as? [String: AnyJSON] else {
throw typeMismatch("Dictionary", actual: JSON, keyPath: nil)
}

var result = [String: T](minimumCapacity: dictionary.count)
try dictionary.forEach { key, value in
result[key] = try decodeValue(value) as T
}
return result
return try [String: T].decode(JSON)
}

/// - Throws: DecodeError
public func decodeDictionary<T: Decodable>(JSON: AnyJSON, rootKeyPath: KeyPath) throws -> [String: T] {
return try decodeValue(JSON) <|-| rootKeyPath
return try [String: T].decode(JSON, rootKeyPath: rootKeyPath)
}

// MARK: - Deprecated
Expand Down
18 changes: 9 additions & 9 deletions Tests/Himotoki/DecodableTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class DecodableTest: XCTestCase {
var JSON = personJSON

// Succeeding case
let person: Person? = try? decodeValue(JSON)
let person = try? Person.decodeValue(JSON)
XCTAssert(person != nil)
XCTAssert(person?.firstName == "ABC")
XCTAssert(person?.lastName == "DEF")
Expand Down Expand Up @@ -78,7 +78,7 @@ class DecodableTest: XCTestCase {
do {
JSON["bool"] = nil
JSON["group"] = nil
try decodeValue(JSON) as Person
try Person.decodeValue(JSON)
} catch let DecodeError.MissingKeyPath(keyPath) {
XCTAssert(keyPath == "bool")
} catch {
Expand All @@ -87,7 +87,7 @@ class DecodableTest: XCTestCase {

do {
JSON["age"] = "foo_bar"
try decodeValue(JSON) as Person
try Person.decodeValue(JSON)
} catch let DecodeError.TypeMismatch(expected, actual, keyPath) {
XCTAssertEqual(keyPath, "age")
XCTAssertTrue(actual.containsString("foo_bar"))
Expand All @@ -102,23 +102,23 @@ class DecodableTest: XCTestCase {
let peopleJSON = Array(count: 500, repeatedValue: personJSON)

measureBlock {
let _: [Person]? = try? decodeArray(peopleJSON)
_ = try? [Person].decode(peopleJSON)
}
}
#endif

func testGroup() {
var JSON: JSONDictionary = [ "name": "Himotoki", "floor": 12 ]

let g: Group? = try? decodeValue(JSON)
let g = try? Group.decodeValue(JSON)
XCTAssert(g != nil)
XCTAssert(g?.name == "Himotoki")
XCTAssert(g?.floor == 12)
XCTAssert(g?.optional == nil)

JSON["name"] = nil
do {
try decodeValue(JSON) as Group
try Group.decodeValue(JSON)
} catch let DecodeError.MissingKeyPath(keyPath) {
XCTAssert(keyPath == "name")
} catch {
Expand All @@ -130,7 +130,7 @@ class DecodableTest: XCTestCase {
let JSON: JSONDictionary = [ "name": "Himotoki", "floor": 12 ]
let array: JSONArray = [ JSON, JSON ]

let values: [Group]? = try? decodeArray(array)
let values = try? [Group].decode(array)
XCTAssert(values != nil)
XCTAssert(values?.count == 2)
}
Expand All @@ -139,7 +139,7 @@ class DecodableTest: XCTestCase {
let JSON: JSONDictionary = [ "name": "Himotoki", "floor": 12 ]
let dictionary: JSONDictionary = [ "1": JSON, "2": JSON ]

let values: [String: Group]? = try? decodeDictionary(dictionary)
let values = try? [String: Group].decode(dictionary)
XCTAssert(values != nil)
XCTAssert(values?.count == 2)
}
Expand All @@ -158,7 +158,7 @@ class DecodableTest: XCTestCase {
"uint64": NSNumber(unsignedLongLong: UInt64.max),
]

let numbers: Numbers? = try? decodeValue(JSON)
let numbers = try? Numbers.decodeValue(JSON)
XCTAssert(numbers != nil)
XCTAssert(numbers?.int == Int.min)
XCTAssert(numbers?.uint == UInt.max)
Expand Down
10 changes: 5 additions & 5 deletions Tests/Himotoki/DecodeErrorTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class DecodeErrorTest: XCTestCase {
func testSuccessOfCastOrFail() {
do {
let d: [String: AnyJSON] = [ "url": "https://swift.org/" ]
_ = try decodeValue(d) as URLHolder
_ = try URLHolder.decodeValue(d)
} catch {
XCTFail("error: \(error)")
}
Expand All @@ -64,7 +64,7 @@ class DecodeErrorTest: XCTestCase {
func testMissingKeyPathInDecodeError() {
do {
let d: [String: AnyJSON] = [ "url": "" ]
let _: URLHolder = try decodeValue(d)
_ = try URLHolder.decodeValue(d)
} catch let DecodeError.MissingKeyPath(keyPath) {
XCTAssertEqual(keyPath, "url")
} catch {
Expand All @@ -74,12 +74,12 @@ class DecodeErrorTest: XCTestCase {

func testMissingKeyPathAndDecodeFailure() {
let d: [String: AnyJSON] = [:]
let a: A = try! decodeValue(d)
let a = try! A.decodeValue(d)
XCTAssertNil(a.b)

do {
let d: [String: AnyJSON] = [ "b": [:] as JSONDictionary ]
let _: A = try decodeValue(d)
_ = try A.decodeValue(d)
XCTFail("DecodeError.MissingKeyPath should be thrown if decoding optional value failed")
} catch let DecodeError.MissingKeyPath(keyPath) {
XCTAssertEqual(keyPath, [ "b", "string" ])
Expand All @@ -91,7 +91,7 @@ class DecodeErrorTest: XCTestCase {
func testCustomError() {
do {
let d: [String: AnyJSON] = [ "url": "file:///Users/foo/bar" ]
let _: URLHolder = try decodeValue(d)
_ = try URLHolder.decodeValue(d)
} catch let DecodeError.Custom(message) {
XCTAssertEqual(message, "File URL is not supported")
} catch {
Expand Down
4 changes: 2 additions & 2 deletions Tests/Himotoki/NestedObjectParsingTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ class NestedObjectParsingTest: XCTestCase {

func testParseNestedObjectSuccess() {
let JSON: JSONDictionary = [ "nested": [ "name": "Foo Bar" ] as JSONDictionary ]
let success: WithNestedObject? = try? decodeValue(JSON)
let success = try? WithNestedObject.decodeValue(JSON)
XCTAssertNotNil(success)
XCTAssertEqual(success?.nestedName, "Foo Bar")
}

func testParseNestedObjectFailure() {
let JSON: JSONDictionary = [ "nested": "Foo Bar" ]
let failure: WithNestedObject? = try? decodeValue(JSON)
let failure = try? WithNestedObject.decodeValue(JSON)
XCTAssertNil(failure)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/Himotoki/RawRepresentableTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class RawRepresentableTest: XCTestCase {
"double_2": 4.0,
]

let e: Extractor = try! decodeValue(JSON)
let e = try! Extractor.decodeValue(JSON)

let A: StringEnum? = try? e <| "string_1"
let D: StringEnum? = try? e <| "string_2"
Expand Down
4 changes: 2 additions & 2 deletions Tests/Himotoki/TransformerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class TransformerTest: XCTestCase {
"dictionaryOptional": [ "a": URLString, "b": URLString ] as JSONDictionary,
]

guard let decoded: URLsByTransformer = try? decodeValue(JSON) else {
guard let decoded = try? URLsByTransformer.decodeValue(JSON) else {
XCTFail()
return
}
Expand All @@ -74,7 +74,7 @@ class TransformerTest: XCTestCase {
]

do {
_ = try decodeValue(JSON) as URLsByTransformer
_ = try URLsByTransformer.decodeValue(JSON)
} catch let DecodeError.Custom(message) {
XCTAssertEqual(message, "Invalid URL string: \(URLString)")
} catch {
Expand Down

0 comments on commit 4357805

Please sign in to comment.