Skip to content

Commit

Permalink
Introduce DecodedType typealias
Browse files Browse the repository at this point in the history
Introducing this typealias helps when trying to conform a non-final
class to `JSONDecodable`.

If we just use `Self`, we run into an issue where the compiler can't
ensure that return type is correct, because the class might be
subclassed. By letting classes explicitly define their decoded type, we
can make the compiler happy while maintaining the ability to subclass if
that's what we want.

As an added benefit, we can default `DecodedType` to `Self`, which means
that `structs` and `final` classes don't need to worry about changing
anything from the way they are doing it now.
  • Loading branch information
gfontenot committed Jan 8, 2015
1 parent fcfe190 commit 0ad59fa
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 10 deletions.
12 changes: 12 additions & 0 deletions Argo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
EAD9FB1A19D4B23F0031E006 /* JSONValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD9FB1919D4B23F0031E006 /* JSONValue.swift */; };
EADADCB21A5DB6F600B180EC /* EquatableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADADCB11A5DB6F600B180EC /* EquatableSpec.swift */; };
EADADCB41A5DB7F800B180EC /* EquatableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADADCB11A5DB6F600B180EC /* EquatableSpec.swift */; };
F802D4C31A5EE061005E236C /* NSURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = F802D4C21A5EE061005E236C /* NSURL.swift */; };
F802D4C41A5EE172005E236C /* NSURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = F802D4C21A5EE061005E236C /* NSURL.swift */; };
F802D4C61A5EE2D5005E236C /* url.json in Resources */ = {isa = PBXBuildFile; fileRef = F802D4C51A5EE2D5005E236C /* url.json */; };
F802D4C71A5EE2D5005E236C /* url.json in Resources */ = {isa = PBXBuildFile; fileRef = F802D4C51A5EE2D5005E236C /* url.json */; };
F862E0AA1A519D360093B028 /* JSONValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD9FB1919D4B23F0031E006 /* JSONValue.swift */; };
F862E0AB1A519D470093B028 /* TypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA08313019D5EEAF003B90D7 /* TypeTests.swift */; };
F862E0AC1A519D520093B028 /* post_comments.json in Resources */ = {isa = PBXBuildFile; fileRef = EA08313219D5EEF2003B90D7 /* post_comments.json */; };
Expand Down Expand Up @@ -119,6 +123,8 @@
EAD9FB1719D49A3E0031E006 /* TestModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestModel.swift; path = ../../../Argo/ArgoTests/Models/TestModel.swift; sourceTree = "<group>"; };
EAD9FB1919D4B23F0031E006 /* JSONValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSONValue.swift; path = ../../../Argo/Argo/Globals/JSONValue.swift; sourceTree = "<group>"; };
EADADCB11A5DB6F600B180EC /* EquatableSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquatableSpec.swift; sourceTree = "<group>"; };
F802D4C21A5EE061005E236C /* NSURL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSURL.swift; sourceTree = "<group>"; };
F802D4C51A5EE2D5005E236C /* url.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = url.json; sourceTree = "<group>"; };
F89335541A4CE83000B88685 /* Argo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Argo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F893355E1A4CE83000B88685 /* Argo-MacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Argo-MacTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
F893356D1A4CE8FC00B88685 /* Argo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Argo.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -259,6 +265,7 @@
EAD9FAFF19D211630031E006 /* Comment.swift */,
EAD9FB0119D211C10031E006 /* Post.swift */,
EAD9FB1719D49A3E0031E006 /* TestModel.swift */,
F802D4C21A5EE061005E236C /* NSURL.swift */,
);
path = Models;
sourceTree = "<group>";
Expand All @@ -269,6 +276,7 @@
EAD9FB0F19D21AF50031E006 /* JSONFileReader.swift */,
EAD9FB0419D2143A0031E006 /* user_with_email.json */,
EAD9FB0819D214AA0031E006 /* user_without_email.json */,
F802D4C51A5EE2D5005E236C /* url.json */,
EAD9FB0919D214AA0031E006 /* TemplateIcon2x.png */,
EAD9FB1319D30ED00031E006 /* comment.json */,
EAD9FB1519D30F8D0031E006 /* post_no_comments.json */,
Expand Down Expand Up @@ -455,6 +463,7 @@
EA395DC41A52F8EB00EB607E /* array_root.json in Resources */,
F8E33FA51A51E0C20025A6E5 /* post_bad_comments.json in Resources */,
EAD9FB1419D30ED00031E006 /* comment.json in Resources */,
F802D4C61A5EE2D5005E236C /* url.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -480,6 +489,7 @@
EA395DC51A52F8EE00EB607E /* array_root.json in Resources */,
EA395DC21A5209C000EB607E /* post_bad_comments.json in Resources */,
F8EF75701A4CEC7100BDCC2D /* comment.json in Resources */,
F802D4C71A5EE2D5005E236C /* url.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -523,6 +533,7 @@
buildActionMask = 2147483647;
files = (
EAD9FB1219D30E660031E006 /* EmbeddedJSONDecodingTests.swift in Sources */,
F802D4C31A5EE061005E236C /* NSURL.swift in Sources */,
EAD9FB1019D21AF50031E006 /* JSONFileReader.swift in Sources */,
EAD9FB0219D211C10031E006 /* Post.swift in Sources */,
EAD9FB1819D49A3E0031E006 /* TestModel.swift in Sources */,
Expand Down Expand Up @@ -555,6 +566,7 @@
buildActionMask = 2147483647;
files = (
F8EF75731A4CEC7800BDCC2D /* Comment.swift in Sources */,
F802D4C41A5EE172005E236C /* NSURL.swift in Sources */,
F8EF75721A4CEC7800BDCC2D /* User.swift in Sources */,
F8EF756B1A4CEC6400BDCC2D /* EmbeddedJSONDecodingTests.swift in Sources */,
F8EF75751A4CEC7800BDCC2D /* TestModel.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Argo/Globals/JSONValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public enum JSONValue {
return keys.reduce(self) { $0?[$1] }
}

public static func map<A: JSONDecodable>(value: JSONValue) -> [A]? {
public static func map<A where A: JSONDecodable, A == A.DecodedType>(value: JSONValue) -> [A]? {
switch value {
case let .JSONArray(a):
return a.reduce([]) { curry(+) <^> $0 <*> (pure <^> A.decode($1)) }
Expand Down
16 changes: 8 additions & 8 deletions Argo/Operators/JSONOperators.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// MARK: Values

// Pull embedded value from JSON
public func <|<A: JSONDecodable>(json: JSONValue, keys: [String]) -> A? {
public func <|<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, keys: [String]) -> A? {
if let o = json.find(keys) {
return A.decode(o)
}
Expand All @@ -10,39 +10,39 @@ public func <|<A: JSONDecodable>(json: JSONValue, keys: [String]) -> A? {
}

// Pull value from JSON
public func <|<A: JSONDecodable>(json: JSONValue, key: String) -> A? {
public func <|<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, key: String) -> A? {
return json <| [key]
}

// Pull embedded optional value from JSON
public func <|?<A: JSONDecodable>(json: JSONValue, keys: [String]) -> A?? {
public func <|?<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, keys: [String]) -> A?? {
return pure(json <| keys)
}

// Pull optional value from JSON
public func <|?<A: JSONDecodable>(json: JSONValue, key: String) -> A?? {
public func <|?<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, key: String) -> A?? {
return json <|? [key]
}

// MARK: Arrays

// Pull embedded array from JSON
public func <||<A: JSONDecodable>(json: JSONValue, keys: [String]) -> [A]? {
public func <||<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, keys: [String]) -> [A]? {
return json.find(keys) >>- JSONValue.map
}

// Pull array from JSON
public func <||<A: JSONDecodable>(json: JSONValue, key: String) -> [A]? {
public func <||<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, key: String) -> [A]? {
return json <|| [key]
}


// Pull embedded optional array from JSON
public func <||?<A: JSONDecodable>(json: JSONValue, keys: [String]) -> [A]?? {
public func <||?<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, keys: [String]) -> [A]?? {
return pure(json <|| keys)
}

// Pull optional array from JSON
public func <||?<A: JSONDecodable>(json: JSONValue, key: String) -> [A]?? {
public func <||?<A where A: JSONDecodable, A == A.DecodedType>(json: JSONValue, key: String) -> [A]?? {
return json <||? [key]
}
3 changes: 2 additions & 1 deletion Argo/Protocols/JSONDecodable.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
public protocol JSONDecodable {
class func decode(JSONValue) -> Self?
typealias DecodedType = Self
class func decode(JSONValue) -> DecodedType?
}
3 changes: 3 additions & 0 deletions ArgoTests/JSON/url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"url": "http://example.com",
}
13 changes: 13 additions & 0 deletions ArgoTests/Models/NSURL.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Argo
import Foundation

extension NSURL: JSONDecodable {
public typealias DecodedType = NSURL

public class func decode(j: JSONValue) -> DecodedType? {
switch j {
case .JSONString(let url): return NSURL(string: url)
default: return .None
}
}
}
8 changes: 8 additions & 0 deletions ArgoTests/Tests/ExampleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ class ExampleTests: XCTestCase {
XCTAssert(user?.email != nil)
XCTAssert(user?.email! == "[email protected]")
}

func testDecodingNonFinalClass() {
let json: AnyObject? = JSONFileReader.JSON(fromFile: "url")
let url: NSURL? = json >>- JSONValue.parse >>- { $0["url"] >>- NSURL.decode }

XCTAssert(url != nil)
XCTAssert(url?.absoluteString == "http://example.com")
}
}

0 comments on commit 0ad59fa

Please sign in to comment.