Skip to content

Commit

Permalink
Merge pull request #1 from wieweb/rootkey
Browse files Browse the repository at this point in the history
parse model from rootkey
  • Loading branch information
gunterhager committed Oct 13, 2017
2 parents e40098d + 306be5c commit d98c4a5
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 16 deletions.
16 changes: 10 additions & 6 deletions ReactiveCodable.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
1979FA671F90BC1D002C7CA6 /* user_rootkey.json in Resources */ = {isa = PBXBuildFile; fileRef = 1979FA661F909DBD002C7CA6 /* user_rootkey.json */; };
BC0CD85A1F30A374000998AA /* ReactiveCodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC48149F1F307CC900AF4C02 /* ReactiveCodable.framework */; };
BC0CD85B1F30A374000998AA /* ReactiveCodable.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BC48149F1F307CC900AF4C02 /* ReactiveCodable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BC0CD8601F30A486000998AA /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC4814EF1F308D6600AF4C02 /* ReactiveSwift.framework */; };
Expand Down Expand Up @@ -72,6 +73,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
1979FA661F909DBD002C7CA6 /* user_rootkey.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = user_rootkey.json; sourceTree = "<group>"; };
BC0CD86E1F30B10D000998AA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
BC48149F1F307CC900AF4C02 /* ReactiveCodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveCodable.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BC4814A21F307CC900AF4C02 /* ReactiveCodable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactiveCodable.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -202,6 +204,7 @@
BC4814D61F307E1200AF4C02 /* JSON */ = {
isa = PBXGroup;
children = (
1979FA661F909DBD002C7CA6 /* user_rootkey.json */,
BC4814D91F307E3700AF4C02 /* tasks_invalid.json */,
BC4814DC1F307E3700AF4C02 /* tasks.json */,
BC4814DA1F307E3700AF4C02 /* user_invalid.json */,
Expand Down Expand Up @@ -346,6 +349,7 @@
buildActionMask = 2147483647;
files = (
BC4814E61F307E4200AF4C02 /* user_invalid.json in Resources */,
1979FA671F90BC1D002C7CA6 /* user_rootkey.json in Resources */,
BC4814E51F307E4200AF4C02 /* tasks.json in Resources */,
BC4814E11F307E4200AF4C02 /* tasks_invalid.json in Resources */,
BC4814E81F307E4200AF4C02 /* user.json in Resources */,
Expand Down Expand Up @@ -571,7 +575,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 49Y4WR525P;
DEVELOPMENT_TEAM = M8F9QH57A6;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
Expand All @@ -597,7 +601,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 49Y4WR525P;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
Expand All @@ -620,7 +624,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
DEVELOPMENT_TEAM = 49Y4WR525P;
DEVELOPMENT_TEAM = M8F9QH57A6;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/iOS",
Expand All @@ -639,7 +643,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
DEVELOPMENT_TEAM = 49Y4WR525P;
DEVELOPMENT_TEAM = M8F9QH57A6;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/iOS",
Expand All @@ -659,7 +663,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = 49Y4WR525P;
DEVELOPMENT_TEAM = M8F9QH57A6;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/iOS",
Expand All @@ -679,7 +683,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = 49Y4WR525P;
DEVELOPMENT_TEAM = M8F9QH57A6;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/iOS",
Expand Down
59 changes: 59 additions & 0 deletions ReactiveCodable/ReactiveCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@ public let ReactiveCodableErrorDomain = "name.gunterhager.ReactiveCodable.ErrorD
public enum ReactiveCodableError: Error {
case decoding(DecodingError)
case underlying(Error)
case invalidRootKey

public var nsError: NSError {
switch self {
case let .decoding(error):
return error as NSError
case let .underlying(error):
return error as NSError
default:
return NSError(domain: ReactiveCodableErrorDomain, code: -1, userInfo: nil)
}
}
}

let userInfoRootKey = CodingUserInfoKey(rawValue: "rootKey")!

// MARK: Signal

extension SignalProtocol where Value == Data {
Expand All @@ -48,6 +53,20 @@ extension SignalProtocol where Value == Data {
}
}


public func mapToType<T: Decodable>(_ type: T.Type, rootKey: CodingKey, decoder: JSONDecoder = JSONDecoder()) -> Signal<T, ReactiveCodableError> {
return signal
.mapError { ReactiveCodableError.underlying($0) }
.attemptMap { json -> Result<T, ReactiveCodableError> in
guard let key = RootKey(key: rootKey) else { return .failure(ReactiveCodableError.invalidRootKey) }
return unwrapThrowableResult {
decoder.userInfo = [userInfoRootKey: key]
let result = try decoder.decode(ContainerModel<T>.self, from: json)
return result.nestedModel
}
}
}

}

// MARK: SignalProducer
Expand All @@ -64,6 +83,10 @@ extension SignalProducerProtocol where Value == Data {
return producer.lift { $0.mapToType(type, decoder: decoder) }
}

public func mapToType<T: Decodable>(_ type: T.Type, rootKey: CodingKey, decoder: JSONDecoder = JSONDecoder()) -> SignalProducer<T, ReactiveCodableError> {
return producer.lift { $0.mapToType(type, rootKey: rootKey, decoder: decoder) }
}

}

// MARK: Helper
Expand All @@ -80,3 +103,39 @@ private func unwrapThrowableResult<T>(throwable: () throws -> T) -> Result<T, Re
}
}
}

// MARK: RootKey Helper

struct RootKey: CodingKey {

var intValue: Int?
var stringValue: String

init?(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}

init?(stringValue: String) {
self.stringValue = stringValue
}

init?(key: CodingKey) {
if let intValue = key.intValue {
self.init(intValue: intValue)
} else {
self.init(stringValue: key.stringValue)
}
}
}

struct ContainerModel<T: Decodable>: Decodable {

let nestedModel: T

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKey.self)
guard let rootKey = decoder.userInfo[userInfoRootKey] as? RootKey else { throw ReactiveCodableError.invalidRootKey }
self.nestedModel = try container.decode(T.self, forKey: rootKey)
}
}
8 changes: 4 additions & 4 deletions ReactiveCodableTests/JSON/tasks.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[
{
"name": "Task 1"
"title": "Task 1"
},
{
"name": "Task 2"
"title": "Task 2"
},
{
"name": "Task 3"
"title": "Task 3"
}
]
]
8 changes: 4 additions & 4 deletions ReactiveCodableTests/JSON/tasks_invalid.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[
{
"name": null
"title": null
},
{
"name": "Task 2"
"title": "Task 2"
},
{
"name": "Task 3"
"title": "Task 3"
}
]
]
19 changes: 19 additions & 0 deletions ReactiveCodableTests/JSON/user_rootkey.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"users": [
{
"name": "Matthias"
},
{
"name": "Gunter"
},
{
"name": "Stefan"
}
],
"user": {
"name": "Alex"
},
"task": {
"title": "Task1"
}
}
2 changes: 1 addition & 1 deletion ReactiveCodableTests/Models/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
import Foundation

struct Task: Codable {
let name: String
let title: String
}
27 changes: 26 additions & 1 deletion ReactiveCodableTests/ReactiveCodableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,31 @@ class ReactiveCodableTests: XCTestCase {
XCTAssertTrue((tasks!).count == 3, "mapJSON returned wrong number of tasks")
}


enum RootKey: String, CodingKey {
case user
case users
case task
case invalid
}

func testMapUserToObjectRootKey() {
var user: User?
mockData.load("user_rootkey")
.mapToType(User.self, rootKey: RootKey.user)
.startWithResult { user = $0.value }
XCTAssertNotNil(user, "mapToType should not return nil user")
}

func testMapUserArrayToObjectRootKey() {
var users: [User]?
mockData.load("user_rootkey")
.mapToType([User].self, rootKey: RootKey.users)
.startWithResult { result in
users = result.value
}

XCTAssertNotNil(users, "mapToType should not return nil user")
XCTAssertTrue((users!).count == 3, "mapToType returned wrong number of users")
}

}

0 comments on commit d98c4a5

Please sign in to comment.