From 7e1dcace9c57761613adbb32363ae0f989435c3a Mon Sep 17 00:00:00 2001 From: "tattn (Tatsuya Tanaka)" Date: Fri, 2 Mar 2018 02:31:37 +0900 Subject: [PATCH] Add ObjectMerger --- MoreCodable.xcodeproj/project.pbxproj | 8 ++++ Sources/DictionaryEncoder.swift | 68 ++++++++++++++++++++------- Sources/ObjectMerger.swift | 38 +++++++++++++++ Tests/ObjectMergerTests.swift | 47 ++++++++++++++++++ 4 files changed, 145 insertions(+), 16 deletions(-) create mode 100644 Sources/ObjectMerger.swift create mode 100644 Tests/ObjectMergerTests.swift diff --git a/MoreCodable.xcodeproj/project.pbxproj b/MoreCodable.xcodeproj/project.pbxproj index a6cc6d2..bb518d7 100644 --- a/MoreCodable.xcodeproj/project.pbxproj +++ b/MoreCodable.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 24028DD0204856B400721297 /* ObjectMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24028DCF204856B400721297 /* ObjectMerger.swift */; }; + 24028DD2204859A400721297 /* ObjectMergerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24028DD1204859A400721297 /* ObjectMergerTests.swift */; }; 242C3E262030CBE500AAA577 /* URLQueryItemsEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242C3E252030CBE500AAA577 /* URLQueryItemsEncoder.swift */; }; 242C3E292030D70300AAA577 /* URLQueryItemsEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242C3E272030D68900AAA577 /* URLQueryItemsEncoderTests.swift */; }; 242C3E2B2030D83600AAA577 /* URLQueryItemsDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242C3E2A2030D83600AAA577 /* URLQueryItemsDecoder.swift */; }; @@ -40,6 +42,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 24028DCF204856B400721297 /* ObjectMerger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectMerger.swift; sourceTree = ""; }; + 24028DD1204859A400721297 /* ObjectMergerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectMergerTests.swift; sourceTree = ""; }; 242C3E252030CBE500AAA577 /* URLQueryItemsEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLQueryItemsEncoder.swift; sourceTree = ""; }; 242C3E272030D68900AAA577 /* URLQueryItemsEncoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLQueryItemsEncoderTests.swift; sourceTree = ""; }; 242C3E2A2030D83600AAA577 /* URLQueryItemsDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLQueryItemsDecoder.swift; sourceTree = ""; }; @@ -115,6 +119,7 @@ 242C3E252030CBE500AAA577 /* URLQueryItemsEncoder.swift */, 242C3E2A2030D83600AAA577 /* URLQueryItemsDecoder.swift */, 242C3E2C2030DDDE00AAA577 /* URLQueryItem+.swift */, + 24028DCF204856B400721297 /* ObjectMerger.swift */, 2491406C2039DF1D00D3E4CD /* Failable.swift */, 249140702039E27F00D3E4CD /* StringTo.swift */, 2491407E203C85A500D3E4CD /* RuleBasedCodingKey.swift */, @@ -132,6 +137,7 @@ 2491406E2039DF7B00D3E4CD /* FailableTests.swift */, 2491407C203C7D5200D3E4CD /* StringToTests.swift */, 24914080203C89D000D3E4CD /* RuleBasedCodingKeyTests.swift */, + 24028DD1204859A400721297 /* ObjectMergerTests.swift */, 24A4FF4020302322001618E1 /* Products */, 24A4FF5820302490001618E1 /* Info.plist */, ); @@ -255,6 +261,7 @@ 249140712039E27F00D3E4CD /* StringTo.swift in Sources */, 242C3E2D2030DDDE00AAA577 /* URLQueryItem+.swift in Sources */, 24A4FF4D20302407001618E1 /* AnyCodingKey.swift in Sources */, + 24028DD0204856B400721297 /* ObjectMerger.swift in Sources */, 242C3E2B2030D83600AAA577 /* URLQueryItemsDecoder.swift in Sources */, 242C3E262030CBE500AAA577 /* URLQueryItemsEncoder.swift in Sources */, 24A4FF4B203023E6001618E1 /* Storage.swift in Sources */, @@ -273,6 +280,7 @@ 242C3E292030D70300AAA577 /* URLQueryItemsEncoderTests.swift in Sources */, 242C3E2F2030E28500AAA577 /* URLQueryItemsDecoderTests.swift in Sources */, 24A4FF5720302490001618E1 /* DictionaryEncoderTests.swift in Sources */, + 24028DD2204859A400721297 /* ObjectMergerTests.swift in Sources */, 2491407D203C7D5200D3E4CD /* StringToTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/DictionaryEncoder.swift b/Sources/DictionaryEncoder.swift index 2195087..0c16424 100644 --- a/Sources/DictionaryEncoder.swift +++ b/Sources/DictionaryEncoder.swift @@ -24,7 +24,7 @@ open class DictionaryEncoder: Encoder { } open func singleValueContainer() -> SingleValueEncodingContainer { - return UnkeyedContanier(encoder: self, codingPath: codingPath) + return SingleValueContanier(encoder: self, codingPath: codingPath) } private func box(_ value: T) throws -> Any { @@ -116,11 +116,11 @@ extension DictionaryEncoder { } } - private class UnkeyedContanier: UnkeyedEncodingContainer, SingleValueEncodingContainer { + private class UnkeyedContanier: UnkeyedEncodingContainer { var encoder: DictionaryEncoder private(set) var codingPath: [CodingKey] private var storage: Storage - var count: Int { return (storage.last as? [Any])?.count ?? 0 } + var count: Int { return storage.count } init(encoder: DictionaryEncoder, codingPath: [CodingKey]) { self.encoder = encoder @@ -145,19 +145,19 @@ extension DictionaryEncoder { func encodeNil() throws {} func encode(_ value: Bool) throws {} - func encode(_ value: Int) throws { push(value) } - func encode(_ value: Int8) throws { push(value) } - func encode(_ value: Int16) throws { push(value) } - func encode(_ value: Int32) throws { push(value) } - func encode(_ value: Int64) throws { push(value) } - func encode(_ value: UInt) throws { push(value) } - func encode(_ value: UInt8) throws { push(value) } - func encode(_ value: UInt16) throws { push(value) } - func encode(_ value: UInt32) throws { push(value) } - func encode(_ value: UInt64) throws { push(value) } - func encode(_ value: Float) throws { push(value) } - func encode(_ value: Double) throws { push(value) } - func encode(_ value: String) throws { push(value) } + func encode(_ value: Int) throws { push(try encoder.box(value)) } + func encode(_ value: Int8) throws { push(try encoder.box(value)) } + func encode(_ value: Int16) throws { push(try encoder.box(value)) } + func encode(_ value: Int32) throws { push(try encoder.box(value)) } + func encode(_ value: Int64) throws { push(try encoder.box(value)) } + func encode(_ value: UInt) throws { push(try encoder.box(value)) } + func encode(_ value: UInt8) throws { push(try encoder.box(value)) } + func encode(_ value: UInt16) throws { push(try encoder.box(value)) } + func encode(_ value: UInt32) throws { push(try encoder.box(value)) } + func encode(_ value: UInt64) throws { push(try encoder.box(value)) } + func encode(_ value: Float) throws { push(try encoder.box(value)) } + func encode(_ value: Double) throws { push(try encoder.box(value)) } + func encode(_ value: String) throws { push(try encoder.box(value)) } func encode(_ value: T) throws { encoder.codingPath.append(AnyCodingKey(index: count)) defer { encoder.codingPath.removeLast() } @@ -181,4 +181,40 @@ extension DictionaryEncoder { return encoder } } + + private class SingleValueContanier: SingleValueEncodingContainer { + var encoder: DictionaryEncoder + private(set) var codingPath: [CodingKey] + private var storage: Storage + var count: Int { return storage.count } + + init(encoder: DictionaryEncoder, codingPath: [CodingKey]) { + self.encoder = encoder + self.codingPath = codingPath + self.storage = encoder.storage + } + + private func push(_ value: Any) { + guard var array = storage.popContainer() as? [Any] else { assertionFailure(); return } + array.append(value) + storage.push(container: array) + } + + func encodeNil() throws {} + func encode(_ value: Bool) throws { storage.push(container: value) } + func encode(_ value: Int) throws { storage.push(container: value) } + func encode(_ value: Int8) throws { storage.push(container: value) } + func encode(_ value: Int16) throws { storage.push(container: value) } + func encode(_ value: Int32) throws { storage.push(container: value) } + func encode(_ value: Int64) throws { storage.push(container: value) } + func encode(_ value: UInt) throws { storage.push(container: value) } + func encode(_ value: UInt8) throws { storage.push(container: value) } + func encode(_ value: UInt16) throws { storage.push(container: value) } + func encode(_ value: UInt32) throws { storage.push(container: value) } + func encode(_ value: UInt64) throws { storage.push(container: value) } + func encode(_ value: Float) throws { storage.push(container: value) } + func encode(_ value: Double) throws { storage.push(container: value) } + func encode(_ value: String) throws { storage.push(container: value) } + func encode(_ value: T) throws { storage.push(container: try encoder.box(value)) } + } } diff --git a/Sources/ObjectMerger.swift b/Sources/ObjectMerger.swift new file mode 100644 index 0000000..14e02dd --- /dev/null +++ b/Sources/ObjectMerger.swift @@ -0,0 +1,38 @@ +// +// ObjectMerger.swift +// MoreCodable +// +// Created by Tatsuya Tanaka on 20180302. +// Copyright © 2018年 tattn. All rights reserved. +// + +import Foundation + +public struct ObjectMerger { + private let encoder = DictionaryEncoder() + private let decoder = DictionaryDecoder() + + public init() {} + + public func merge(_ type: T.Type = T.self, _ aObject: A, _ bObject: B) throws -> T { + print(try encoder.encode(bObject)) + let dictionary = try encoder.encode(aObject) + .merging(try encoder.encode(bObject)) { left, _ in left } + return try decoder.decode(T.self, from: dictionary) + } + + public func merge(_ type: T.Type = T.self, _ aObject: A, _ bObject: B, _ cObject: C) throws -> T { + let dictionary = try encoder.encode(aObject) + .merging(try encoder.encode(bObject)) { left, _ in left } + .merging(try encoder.encode(cObject)) { left, _ in left } + return try decoder.decode(T.self, from: dictionary) + } + + public func merge(_ type: T.Type = T.self, _ aObject: A, _ bObject: B, _ cObject: C, _ dObject: D) throws -> T { + let dictionary = try encoder.encode(aObject) + .merging(try encoder.encode(bObject)) { left, _ in left } + .merging(try encoder.encode(cObject)) { left, _ in left } + .merging(try encoder.encode(dObject)) { left, _ in left } + return try decoder.decode(T.self, from: dictionary) + } +} diff --git a/Tests/ObjectMergerTests.swift b/Tests/ObjectMergerTests.swift new file mode 100644 index 0000000..d820bb4 --- /dev/null +++ b/Tests/ObjectMergerTests.swift @@ -0,0 +1,47 @@ +// +// ObjectMergerTests.swift +// MoreCodableTests +// +// Created by Tatsuya Tanaka on 20180302. +// Copyright © 2018年 tattn. All rights reserved. +// + +import XCTest +import MoreCodable + +class ObjectMergerTests: XCTestCase { + + override func setUp() { + super.setUp() + } + + override func tearDown() { + super.tearDown() + } + + func testSimpleCase() throws { + struct APIResponse: Encodable { + let id: Int + let title: String + let foo: String + } + + struct APIResponse2: Encodable { + let tags: [String] + } + + struct Model: Decodable { + let id: Int + let title: String + let tags: [String] + } + + let response = APIResponse(id: 0, title: "Awesome article", foo: "bar") + let response2 = APIResponse2(tags: ["swift", "ios", "macos"]) + let model = try ObjectMerger().merge(Model.self, response, response2) + + XCTAssertEqual(model.id, response.id) + XCTAssertEqual(model.title, response.title) + XCTAssertEqual(model.tags, response2.tags) + } +}