Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Commit

Permalink
Add new DecoderType protocol. #8
Browse files Browse the repository at this point in the history
`DecoderType` is a new protocol that better encapsulates the idea of decoding an object from a given `JSON`. It allows for two abilities that `JSONDecodable` did not:

1) Multiple decoders for the same type can be defined. This is illustrated by the two `NSDate` decoders provided by Alexander.
2) The decoder type is separate from the type being decoded.

`JSONDecodable` is now deprecated.
  • Loading branch information
calebd committed Nov 19, 2015
1 parent e035967 commit 27543c2
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 85 deletions.
18 changes: 18 additions & 0 deletions Alexander.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
3AE5C3941BFD3EBC00F1080F /* AlexanderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C3931BFD3EBC00F1080F /* AlexanderTests.swift */; };
3AE5C3951BFD3EBC00F1080F /* AlexanderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C3931BFD3EBC00F1080F /* AlexanderTests.swift */; };
3AE5C3961BFD3EBC00F1080F /* AlexanderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C3931BFD3EBC00F1080F /* AlexanderTests.swift */; };
3AE5C39C1BFD522300F1080F /* DecoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C39B1BFD522300F1080F /* DecoderType.swift */; };
3AE5C39D1BFD522300F1080F /* DecoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C39B1BFD522300F1080F /* DecoderType.swift */; };
3AE5C39E1BFD522300F1080F /* DecoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C39B1BFD522300F1080F /* DecoderType.swift */; };
3AE5C39F1BFD522300F1080F /* DecoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C39B1BFD522300F1080F /* DecoderType.swift */; };
3AE5C3A11BFD551800F1080F /* UserDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C3A01BFD551800F1080F /* UserDecoderTests.swift */; };
3AE5C3A21BFD551800F1080F /* UserDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C3A01BFD551800F1080F /* UserDecoderTests.swift */; };
3AE5C3A31BFD551800F1080F /* UserDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5C3A01BFD551800F1080F /* UserDecoderTests.swift */; };
3B62AAD91B82452800491A67 /* Defines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B62AAD81B82452800491A67 /* Defines.swift */; };
3B62AADA1B82452800491A67 /* Defines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B62AAD81B82452800491A67 /* Defines.swift */; };
3B62AADC1B824C1200491A67 /* JSONCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B62AADB1B824C1200491A67 /* JSONCodable.swift */; };
Expand Down Expand Up @@ -71,6 +78,8 @@
3A512F101BE16BB60091A124 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3A512F121BE16BB60091A124 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3AE5C3931BFD3EBC00F1080F /* AlexanderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlexanderTests.swift; sourceTree = "<group>"; };
3AE5C39B1BFD522300F1080F /* DecoderType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecoderType.swift; sourceTree = "<group>"; };
3AE5C3A01BFD551800F1080F /* UserDecoderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDecoderTests.swift; sourceTree = "<group>"; };
3B62AAD81B82452800491A67 /* Defines.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Defines.swift; sourceTree = "<group>"; };
3B62AADB1B824C1200491A67 /* JSONCodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONCodable.swift; sourceTree = "<group>"; };
3BEDAE1F1B5DC30500AD52FE /* Alexander.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alexander.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -187,6 +196,7 @@
isa = PBXGroup;
children = (
3AE5C3931BFD3EBC00F1080F /* AlexanderTests.swift */,
3AE5C3A01BFD551800F1080F /* UserDecoderTests.swift */,
3A512F0C1BE16BB60091A124 /* Configuration */,
);
path = Tests;
Expand Down Expand Up @@ -253,6 +263,7 @@
isa = PBXGroup;
children = (
3BEDAE5C1B5DC4B200AD52FE /* JSON.swift */,
3AE5C39B1BFD522300F1080F /* DecoderType.swift */,
3B62AADB1B824C1200491A67 /* JSONCodable.swift */,
3B62AAD81B82452800491A67 /* Defines.swift */,
3A512EC91BE168C30091A124 /* Configuration */,
Expand Down Expand Up @@ -537,6 +548,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3AE5C39E1BFD522300F1080F /* DecoderType.swift in Sources */,
3A512F051BE16B250091A124 /* JSONCodable.swift in Sources */,
3A512F041BE16B250091A124 /* JSON.swift in Sources */,
3A512F061BE16B250091A124 /* Defines.swift in Sources */,
Expand All @@ -548,13 +560,15 @@
buildActionMask = 2147483647;
files = (
3AE5C3961BFD3EBC00F1080F /* AlexanderTests.swift in Sources */,
3AE5C3A31BFD551800F1080F /* UserDecoderTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
3A512EF01BE169A00091A124 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3AE5C39F1BFD522300F1080F /* DecoderType.swift in Sources */,
3A512F081BE16B250091A124 /* JSONCodable.swift in Sources */,
3A512F071BE16B250091A124 /* JSON.swift in Sources */,
3A512F091BE16B250091A124 /* Defines.swift in Sources */,
Expand All @@ -565,6 +579,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3AE5C39C1BFD522300F1080F /* DecoderType.swift in Sources */,
3B62AAD91B82452800491A67 /* Defines.swift in Sources */,
3B62AADC1B824C1200491A67 /* JSONCodable.swift in Sources */,
3BEDAE5D1B5DC4B200AD52FE /* JSON.swift in Sources */,
Expand All @@ -576,13 +591,15 @@
buildActionMask = 2147483647;
files = (
3AE5C3941BFD3EBC00F1080F /* AlexanderTests.swift in Sources */,
3AE5C3A11BFD551800F1080F /* UserDecoderTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
3BEDAE5F1B5DC4EC00AD52FE /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3AE5C39D1BFD522300F1080F /* DecoderType.swift in Sources */,
3B62AADA1B82452800491A67 /* Defines.swift in Sources */,
3BEDAE7D1B5DC58800AD52FE /* JSON.swift in Sources */,
3A512EC81BE15A380091A124 /* JSONCodable.swift in Sources */,
Expand All @@ -594,6 +611,7 @@
buildActionMask = 2147483647;
files = (
3AE5C3951BFD3EBC00F1080F /* AlexanderTests.swift in Sources */,
3AE5C3A21BFD551800F1080F /* UserDecoderTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
50 changes: 50 additions & 0 deletions Alexander/DecoderType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// DecoderType.swift
// Alexander
//
// Created by Caleb Davenport on 11/18/15.
// Copyright © 2015 Hodinkee. All rights reserved.
//

public protocol DecoderType {
typealias Value
static func decode(JSON: Alexander.JSON) -> Value?
}

extension JSON {
public func decode<T: DecoderType>(decoder: T.Type) -> T.Value? {
return decode(T.decode)
}

public func decodeArray<T: DecoderType>(decoder: T.Type) -> [T.Value]? {
return decodeArray(T.decode)
}
}

public struct NSTimeIntervalDecoder: DecoderType {
public typealias Value = NSTimeInterval
public static func decode(JSON: Alexander.JSON) -> Value? {
return JSON.object as? NSTimeInterval
}
}

public struct NSDateTimeIntervalSince1970Decoder: DecoderType {
public typealias Value = NSDate
public static func decode(JSON: Alexander.JSON) -> Value? {
return NSTimeIntervalDecoder.decode(JSON).flatMap({ NSDate(timeIntervalSince1970: $0) })
}
}

public struct NSDateTimeIntervalSinceReferenceDateDecoder: DecoderType {
public typealias Value = NSDate
public static func decode(JSON: Alexander.JSON) -> Value? {
return NSTimeIntervalDecoder.decode(JSON).flatMap({ NSDate(timeIntervalSinceReferenceDate: $0) })
}
}

public struct NSURLDecoder: DecoderType {
public typealias Value = NSURL
public static func decode(JSON: Alexander.JSON) -> Value? {
return JSON.stringValue.flatMap({ NSURL(string: $0) })
}
}
12 changes: 6 additions & 6 deletions Alexander/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ extension JSON: CustomDebugStringConvertible {
}
return "Invalid JSON."
}

}

extension JSON {
Expand All @@ -129,17 +128,18 @@ extension JSON {
return boolValue
}

@available(*, deprecated, message = "Use decode(NSURL) instead.")
@available(*, deprecated, message = "Use decode(NSURLDecoder) instead.")
public var url: NSURL? {
return decode(NSURL)
return decode(NSURLDecoder)
}

@available(*, deprecated, message = "Use decode(NSTimeIntervalDecoder) instead.")
public var timeInterval: NSTimeInterval? {
return object as? NSTimeInterval
return decode(NSTimeIntervalDecoder)
}

@available(*, deprecated, message = "Use decode(NSDate) instead.")
@available(*, deprecated, message = "Use decode(NSDateTimeIntervalSince1970Decoder) instead.")
public var date: NSDate? {
return decode(NSDate)
return decode(NSDateTimeIntervalSince1970Decoder)
}
}
13 changes: 1 addition & 12 deletions Alexander/JSONCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public protocol JSONEncodable {
var JSON: Alexander.JSON { get }
}

@available(*, deprecated, message = "Use DecoderType instead.")
public protocol JSONDecodable {
static func decode(JSON: Alexander.JSON) -> Self?
}
Expand All @@ -23,15 +24,3 @@ public extension Alexander.JSON {
return decodeArray(T.decode)
}
}

extension NSURL: JSONDecodable {
public static func decode(JSON: Alexander.JSON) -> Self? {
return JSON.stringValue.flatMap(self.init)
}
}

extension NSDate: JSONDecodable {
public static func decode(JSON: Alexander.JSON) -> Self? {
return (JSON.object as? NSTimeInterval).flatMap({ self.init(timeIntervalSince1970: $0) })
}
}
70 changes: 3 additions & 67 deletions Tests/AlexanderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import XCTest
import Alexander

final class AlexanderTests: XCTestCase {

func testArray() {
let JSON = Alexander.JSON(object: ["1","2", "a", "B", "D"])
XCTAssertEqual(JSON.array?.count, 5)
Expand Down Expand Up @@ -83,73 +82,10 @@ final class AlexanderTests: XCTestCase {
XCTAssertEqual(decodedSeasons, [ Season.Winter, Season.Summer, Season.Spring ])
}

func testValidDecodableObjectSingle() {
struct User: JSONDecodable {
var ID: String
var name: String

static func decode(JSON: Alexander.JSON) -> User? {
guard let ID = JSON["id"]?.stringValue, let name = JSON["name"]?.stringValue else {
return nil
}
return User(ID: ID, name: name)
}
}

let user = User(ID: "1", name: "Caleb")
let object = [ "id": user.ID, "name": user.name ]
let JSON = Alexander.JSON(object: object)
guard let decodedUser = JSON.decode(User) else {
XCTFail()
return
}

XCTAssertEqual(decodedUser.ID, user.ID)
XCTAssertEqual(decodedUser.name, user.name)
}

func testValidDecodableObjectArray() {
struct User: JSONDecodable {
var ID: String
var name: String

static func decode(JSON: Alexander.JSON) -> User? {
guard let ID = JSON["id"]?.stringValue, let name = JSON["name"]?.stringValue else {
return nil
}
return User(ID: ID, name: name)
}
}

let users = [
User(ID: "1", name: "Caleb"),
User(ID: "2", name: "Jon")
]
let object = [
"users": [
[ "id": users[0].ID, "name": users[0].name ],
[ "id": users[1].ID, "name": users[1].name ]
]
]
let JSON = Alexander.JSON(object: object)
guard let decodedUsers = JSON["users"]?.decodeArray(User) else {
XCTFail()
return
}

XCTAssertEqual(decodedUsers.count, 2)

XCTAssertEqual(decodedUsers[0].ID, users[0].ID)
XCTAssertEqual(decodedUsers[0].name, users[0].name)

XCTAssertEqual(decodedUsers[1].ID, users[1].ID)
XCTAssertEqual(decodedUsers[1].name, users[1].name)
}

func testURLHelpers() {
let JSON = Alexander.JSON(object: "https://www.hodinkee.com")

guard let URL = JSON.decode(NSURL) else {
guard let URL = JSON.decode(NSURLDecoder) else {
XCTFail()
return
}
Expand All @@ -159,7 +95,7 @@ final class AlexanderTests: XCTestCase {

func testDateHelpers() {
let JSON = Alexander.JSON(object: 978307200)
XCTAssertEqual(JSON.timeInterval, NSDate(timeIntervalSinceReferenceDate: 0).timeIntervalSince1970)
XCTAssertEqual(JSON.decode(NSDate), NSDate(timeIntervalSinceReferenceDate: 0))
XCTAssertEqual(JSON.decode(NSTimeIntervalDecoder), NSDate(timeIntervalSinceReferenceDate: 0).timeIntervalSince1970)
XCTAssertEqual(JSON.decode(NSDateTimeIntervalSince1970Decoder), NSDate(timeIntervalSinceReferenceDate: 0))
}
}
66 changes: 66 additions & 0 deletions Tests/UserDecoderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// UserDecoderTests.swift
// Alexander
//
// Created by Caleb Davenport on 11/18/15.
// Copyright © 2015 Hodinkee. All rights reserved.
//

import XCTest
import Alexander

final class UserDecoderTests: XCTestCase {
func testDecodeValidUser() {
let user = User(ID: "1", name: "Caleb")
let object = [ "id": user.ID, "name": user.name ]
let JSON = Alexander.JSON(object: object)
guard let decodedUser = JSON.decode(UserDecoder) else {
XCTFail()
return
}

XCTAssertEqual(decodedUser.ID, user.ID)
XCTAssertEqual(decodedUser.name, user.name)
}

func testDecodeValidUsersArray() {
let users = [
User(ID: "1", name: "Caleb"),
User(ID: "2", name: "Jon")
]
let object = [
"users": [
[ "id": users[0].ID, "name": users[0].name ],
[ "id": users[1].ID, "name": users[1].name ]
]
]
let JSON = Alexander.JSON(object: object)
guard let decodedUsers = JSON["users"]?.decodeArray(UserDecoder) where decodedUsers.count == 2 else {
XCTFail()
return
}

XCTAssertEqual(decodedUsers.count, 2)

XCTAssertEqual(decodedUsers[0].ID, users[0].ID)
XCTAssertEqual(decodedUsers[0].name, users[0].name)

XCTAssertEqual(decodedUsers[1].ID, users[1].ID)
XCTAssertEqual(decodedUsers[1].name, users[1].name)
}
}

struct User {
var ID: String
var name: String
}

struct UserDecoder: DecoderType {
typealias Value = User
static func decode(JSON: Alexander.JSON) -> Value? {
guard let ID = JSON["id"]?.stringValue, let name = JSON["name"]?.stringValue else {
return nil
}
return User(ID: ID, name: name)
}
}

0 comments on commit 27543c2

Please sign in to comment.