Skip to content

Commit

Permalink
Add CodableAny
Browse files Browse the repository at this point in the history
  • Loading branch information
tattn committed Sep 9, 2018
1 parent 4ecdc4e commit 32a3612
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 0 deletions.
8 changes: 8 additions & 0 deletions MoreCodable.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
24A4FF6720304D8F001618E1 /* DictionaryDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A4FF6520304D8C001618E1 /* DictionaryDecoderTests.swift */; };
24CF7FE42144DC8D007A5C6C /* CodableDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24CF7FE32144DC8D007A5C6C /* CodableDictionary.swift */; };
24CF7FE62144DCA4007A5C6C /* CodableDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24CF7FE52144DCA4007A5C6C /* CodableDictionaryTests.swift */; };
24CF7FE82144E76D007A5C6C /* CodableAny.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24CF7FE72144E76D007A5C6C /* CodableAny.swift */; };
24CF7FEA2144E7DC007A5C6C /* CodableAnyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24CF7FE92144E7DC007A5C6C /* CodableAnyTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -71,6 +73,8 @@
24A4FF6520304D8C001618E1 /* DictionaryDecoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryDecoderTests.swift; sourceTree = "<group>"; };
24CF7FE32144DC8D007A5C6C /* CodableDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableDictionary.swift; sourceTree = "<group>"; };
24CF7FE52144DCA4007A5C6C /* CodableDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableDictionaryTests.swift; sourceTree = "<group>"; };
24CF7FE72144E76D007A5C6C /* CodableAny.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableAny.swift; sourceTree = "<group>"; };
24CF7FE92144E7DC007A5C6C /* CodableAnyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableAnyTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -128,6 +132,7 @@
249140702039E27F00D3E4CD /* StringTo.swift */,
2491407E203C85A500D3E4CD /* RuleBasedCodingKey.swift */,
24CF7FE32144DC8D007A5C6C /* CodableDictionary.swift */,
24CF7FE72144E76D007A5C6C /* CodableAny.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand All @@ -144,6 +149,7 @@
24914080203C89D000D3E4CD /* RuleBasedCodingKeyTests.swift */,
24028DD1204859A400721297 /* ObjectMergerTests.swift */,
24CF7FE52144DCA4007A5C6C /* CodableDictionaryTests.swift */,
24CF7FE92144E7DC007A5C6C /* CodableAnyTests.swift */,
24A4FF4020302322001618E1 /* Products */,
24A4FF5820302490001618E1 /* Info.plist */,
);
Expand Down Expand Up @@ -266,6 +272,7 @@
2491406D2039DF1D00D3E4CD /* Failable.swift in Sources */,
249140712039E27F00D3E4CD /* StringTo.swift in Sources */,
242C3E2D2030DDDE00AAA577 /* URLQueryItem+.swift in Sources */,
24CF7FE82144E76D007A5C6C /* CodableAny.swift in Sources */,
24A4FF4D20302407001618E1 /* AnyCodingKey.swift in Sources */,
24028DD0204856B400721297 /* ObjectMerger.swift in Sources */,
242C3E2B2030D83600AAA577 /* URLQueryItemsDecoder.swift in Sources */,
Expand All @@ -282,6 +289,7 @@
buildActionMask = 2147483647;
files = (
2491406F2039DF7B00D3E4CD /* FailableTests.swift in Sources */,
24CF7FEA2144E7DC007A5C6C /* CodableAnyTests.swift in Sources */,
24CF7FE62144DCA4007A5C6C /* CodableDictionaryTests.swift in Sources */,
24914081203C89D000D3E4CD /* RuleBasedCodingKeyTests.swift in Sources */,
24A4FF6720304D8F001618E1 /* DictionaryDecoderTests.swift in Sources */,
Expand Down
98 changes: 98 additions & 0 deletions Sources/CodableAny.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// CodableAny.swift
// MoreCodable
//
// Created by Tatsuya Tanaka on 20180909.
// Copyright © 2018年 tattn. All rights reserved.
//

import Foundation

public struct CodableAny {
public let value: Any

public init(_ value: Any) {
self.value = value
}
}

extension CodableAny: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

if container.decodeNil() {
self.init(())
} else if let bool = try? container.decode(Bool.self) {
self.init(bool)
} else if let int = try? container.decode(Int.self) {
self.init(int)
} else if let uint = try? container.decode(UInt.self) {
self.init(uint)
} else if let double = try? container.decode(Double.self) {
self.init(double)
} else if let string = try? container.decode(String.self) {
self.init(string)
} else if let array = try? container.decode([CodableAny].self) {
self.init(array.map { $0.value })
} else if let dictionary = try? container.decode([String: CodableAny].self) {
self.init(dictionary.mapValues { $0.value })
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "This type is not supported."
)
}
}
}

extension CodableAny: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()

switch value {
case is Void, Optional<Any>.none:
try container.encodeNil()
case let bool as Bool:
try container.encode(bool)
case let int as Int:
try container.encode(int)
case let int8 as Int8:
try container.encode(int8)
case let int16 as Int16:
try container.encode(int16)
case let int32 as Int32:
try container.encode(int32)
case let int64 as Int64:
try container.encode(int64)
case let uint as UInt:
try container.encode(uint)
case let uint8 as UInt8:
try container.encode(uint8)
case let uint16 as UInt16:
try container.encode(uint16)
case let uint32 as UInt32:
try container.encode(uint32)
case let uint64 as UInt64:
try container.encode(uint64)
case let float as Float:
try container.encode(float)
case let double as Double:
try container.encode(double)
case let string as String:
try container.encode(string)
case let date as Date:
try container.encode(date)
case let url as URL:
try container.encode(url)
case let array as [Any]:
try container.encode(array.map(CodableAny.init))
case let dictionary as [String: Any]:
try container.encode(dictionary.mapValues(CodableAny.init))
default:
throw EncodingError.invalidValue(value, EncodingError.Context(
codingPath: container.codingPath,
debugDescription: "\(type(of: value)) type is not supported."
))
}
}
}
71 changes: 71 additions & 0 deletions Tests/CodableAnyTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// CodableAnyTests.swift
// MoreCodableTests
//
// Created by Tatsuya Tanaka on 20180909.
// Copyright © 2018年 tattn. All rights reserved.
//

import XCTest
import MoreCodable

class CodableAnyTests: XCTestCase {
let jsonEncoder = JSONEncoder()
let jsonDecoder = JSONDecoder()

override func setUp() {
super.setUp()
}

func testInt() {
assertEncodingAndDecoding(["value": 1])
}

func testStringIntDictionary() {
assertEncodingAndDecoding(["value": ["key": 1]])
}

func testDoubleArray() {
assertEncodingAndDecoding([1.1, 2.2, 3.3])

let decodedValue = encodeAndDecode([1, 2, 3] as [Double])
XCTAssertEqual(decodedValue as! [Int], [1, 2, 3]) // double to int
}

func testNestedArray() {
assertEncodingAndDecoding([[[["one", "two", "three"]]]])
}

func testOptional() {
let decodedValue = encodeAndDecode([nil, 1, nil])
let values = decodedValue as! [Any]
XCTAssertNotNil(values[0])
XCTAssertTrue(values[0] is Void)
XCTAssertEqual(values[1] as! Int, 1)
XCTAssertNotNil(values[2])
XCTAssertTrue(values[2] is Void)
}

func testDate() {
let date = Date()
let decodedValue = encodeAndDecode(["key": date])
XCTAssertEqual(decodedValue as! [String: Double], ["key": date.timeIntervalSinceReferenceDate]) // date to double
}

func testURL() {
let url = URL(string: "https://example.com")!
let decodedValue = encodeAndDecode(["key": url])
XCTAssertEqual(decodedValue as! [String: String], ["key": url.absoluteString]) // url to string
}

private func encodeAndDecode(_ value: Any) -> Any {
let _any = CodableAny(value)
let data = try! jsonEncoder.encode(_any)
return (try! jsonDecoder.decode(CodableAny.self, from: data)).value
}

private func assertEncodingAndDecoding<T: Equatable>(_ expectedValue: T) {
let decodedValue = encodeAndDecode(expectedValue)
XCTAssertEqual(decodedValue as! T, expectedValue)
}
}

0 comments on commit 32a3612

Please sign in to comment.