├── .swift-version ├── Tests ├── JSONExamples │ ├── NoJsonObject.json │ ├── MissingKey.json │ ├── TypeMismatch.json │ ├── Repository.json │ └── Vehicle.json ├── NSNullTests.swift ├── Info.plist ├── Vehicle.swift ├── MissingKeyOperatorTests.swift ├── DynamicDecodableTests.swift ├── DecodeAsOneOfTests.swift ├── RawRepresentableDecodableTests.swift ├── ParseTests.swift ├── Repository.swift ├── NSValueDecodableTests.swift ├── DictionaryTests.swift ├── KeyPathTests.swift ├── ErrorPathTests.swift ├── DecodableExtensionTests.swift ├── ArrayTests.swift ├── DecodableTests.swift └── DecodableOperatorsTests.swift ├── Sources ├── Playground.playground │ ├── Contents.swift │ └── contents.xcplayground ├── Decodable.h ├── Info.plist ├── RawRepresentableDecodable.swift ├── KeyPath.swift ├── Operators.swift ├── NSValueCastable.swift ├── Decodable.swift ├── Decoders.swift ├── OptionalKeyPath.swift ├── Parse.swift ├── Castable.swift └── DecodingError.swift ├── Package.swift ├── Decodable.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings ├── xcshareddata │ ├── xcbaselines │ │ ├── 8FE7B56B1B4C9FB900837609.xcbaseline │ │ │ ├── 73DA0DF5-ACA7-4948-824F-2F1739DC2034.plist │ │ │ ├── 2B184EB9-20A6-44F1-BFA7-F9185332D574.plist │ │ │ └── Info.plist │ │ └── 17FB80FF1B530FED0012F106.xcbaseline │ │ │ ├── 78C19493-4A8B-4FE0-88D6-957F92628060.plist │ │ │ └── Info.plist │ └── xcschemes │ │ ├── Decodable-tvOS.xcscheme │ │ ├── Decodable-watchOS.xcscheme │ │ ├── Decodable-Mac.xcscheme │ │ └── Decodable-iOS.xcscheme └── project.pbxproj ├── Generator ├── Templates │ ├── Header.swift │ └── Documentation.swift └── Generator.swift ├── .travis.yml ├── Decodable.podspec ├── .gitignore ├── LICENSE └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /Tests/JSONExamples/NoJsonObject.json: -------------------------------------------------------------------------------- 1 | "id" -------------------------------------------------------------------------------- /Sources/Playground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Decodable", 5 | exclude: [ "Tests" ] 6 | ) 7 | -------------------------------------------------------------------------------- /Sources/Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Generator/Templates/Header.swift: -------------------------------------------------------------------------------- 1 | // 2 | // {filename} 3 | // Decodable 4 | // 5 | // Generated automatically by {by} as a build phase. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | // {count} overloads were generated with the following return types: 10 | // {overloads} 11 | -------------------------------------------------------------------------------- /Generator/Templates/Documentation.swift: -------------------------------------------------------------------------------- 1 | /// Retrieves the object at `path` from `json` and decodes it according to the return type 2 | /// 3 | /// - parameter json: An object from NSJSONSerialization, preferably a `NSDictionary`. 4 | /// - parameter path: {path} 5 | /// - throws: {throws} 6 | /// - returns: {returns} 7 | /// 8 | -------------------------------------------------------------------------------- /Tests/JSONExamples/MissingKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Decodable", 3 | "description": "Decodable", 4 | "html_url": "https://github.com/Anviking/Decodable", 5 | "owner": { 6 | "id": 23, 7 | "login": "fjbelchi" 8 | }, 9 | "coverage": 90.9, 10 | "files": ["file1", "file2", "file3"], 11 | "optional": null 12 | } -------------------------------------------------------------------------------- /Tests/JSONExamples/TypeMismatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "23", 3 | "name": "Decodable", 4 | "description": "Decodable", 5 | "html_url": "https://github.com/Anviking/Decodable", 6 | "owner": { 7 | "id": 23, 8 | "login": "fjbelchi" 9 | }, 10 | "coverage": 90.9, 11 | "files": ["file1", "file2", "file3"], 12 | "optional": null 13 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: Decodable.xcodeproj # path to your xcodeproj folder 3 | osx_image: xcode9 4 | script: 5 | - xcodebuild build -project Decodable.xcodeproj/ -scheme 'Decodable-iOS' -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,name=iPhone 6,OS=10.0' 6 | - xcodebuild test -project Decodable.xcodeproj/ -scheme 'Decodable-Mac' ONLY_ACTIVE_ARCH=NO 7 | 8 | -------------------------------------------------------------------------------- /Tests/JSONExamples/Repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 23, 3 | "name": "Decodable", 4 | "description": "Decodable", 5 | "html_url": "https://github.com/Anviking/Decodable", 6 | "owner": { 7 | "id": 23, 8 | "login": "fjbelchi" 9 | }, 10 | "coverage": 90.9, 11 | "files": ["file1", "file2", "file3"], 12 | "optional": null, 13 | "active": true, 14 | "optionalActive": null 15 | } -------------------------------------------------------------------------------- /Tests/JSONExamples/Vehicle.json: -------------------------------------------------------------------------------- 1 | { 2 | "vehicles": [ 3 | { 4 | "type":"train", 5 | "driverless":true, 6 | "electric":true 7 | }, 8 | { 9 | "type":"car", 10 | "driverless":false 11 | }, 12 | { 13 | "type":"truck", 14 | "driverless":false, 15 | "wheels":18 16 | }, 17 | { 18 | "type":"truck", 19 | "driverless":true, 20 | "wheels":18 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /Tests/NSNullTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSNullTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-12-28. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Decodable 11 | 12 | class NSNullTests: XCTestCase { 13 | 14 | // https://github.com/Anviking/Decodable/issues/135 15 | func testNullToAny() { 16 | let json = NSDictionary(dictionary: ["tone": NSNull()]) 17 | let maybeTone: Any? = try! json =>? "tone" 18 | XCTAssertNil(maybeTone) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Decodable.h: -------------------------------------------------------------------------------- 1 | // 2 | // Decodable.h 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-07-08. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Decodable. 12 | FOUNDATION_EXPORT double DecodableVersionNumber; 13 | 14 | //! Project version string for Decodable. 15 | FOUNDATION_EXPORT const unsigned char DecodableVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcbaselines/8FE7B56B1B4C9FB900837609.xcbaseline/73DA0DF5-ACA7-4948-824F-2F1739DC2034.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | DecodableTests 8 | 9 | testDecodeArrayOfRepositoriesSuccess() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.5 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcbaselines/8FE7B56B1B4C9FB900837609.xcbaseline/2B184EB9-20A6-44F1-BFA7-F9185332D574.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | DecodableTests 8 | 9 | testDecodeArrayOfRepositoriesAndMeasureTime() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.10205 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Decodable.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Decodable" 3 | s.version = "0.6.0" 4 | s.summary = "Swift JSON parsing done (more) right" 5 | s.description = "Simple yet powerful object mapping made possible by Swift 2's error handling. Greatly inspired by Argo, but without any functional programming and bizillion operators." 6 | s.homepage = "https://github.com/Anviking/Decodable" 7 | s.license = 'MIT' 8 | s.author = { "Anviking" => "anviking@me.com" } 9 | s.source = { :git => "https://github.com/Anviking/Decodable.git", :tag => "#{s.version}" } 10 | s.ios.deployment_target = '8.0' 11 | s.osx.deployment_target = '10.9' 12 | s.tvos.deployment_target = '9.0' 13 | s.watchos.deployment_target = '2.0' 14 | s.requires_arc = true 15 | s.source_files = 'Sources/*.{swift,h}' 16 | end 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | 35 | *.xcscmblueprint 36 | 37 | # Swift Package Manager 38 | 39 | .build/ 40 | 41 | # General OS X 42 | .DS_Store 43 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcbaselines/17FB80FF1B530FED0012F106.xcbaseline/78C19493-4A8B-4FE0-88D6-957F92628060.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | DecodableTests 8 | 9 | testCustomParseAndMeasureTime() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.01 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | testDecodeArrayOfRepositoriesAndMeasureTime() 20 | 21 | com.apple.XCTPerformanceMetric_WallClockTime 22 | 23 | baselineAverage 24 | 0.23 25 | baselineIntegrationDisplayName 26 | Local Baseline 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Sources/RawRepresentableDecodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RawRepresentableDecodable.swift 3 | // Decodable 4 | // 5 | // Created by Daniel Garbień on 06/11/15. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | /** 10 | * Extends all RawRepresentables (enums) which are also Decodable with decode implementation. 11 | * 12 | * I could not find a way to implicitly declare RawRepresentable conforming to Decodable, what would make all enums Decodable automatically. 13 | * Because of that for an enum to be compatible with Decodable operators it must be declared as implementing Decodable protocol. 14 | */ 15 | public extension RawRepresentable where RawValue: Decodable, Self: Decodable { 16 | 17 | static func decode(_ json: Any) throws -> Self { 18 | let rawValue = try RawValue.decode(json) 19 | guard let rawRepresentable = Self(rawValue: rawValue) else { 20 | let metadata = DecodingError.Metadata(object: json) 21 | throw DecodingError.rawRepresentableInitializationError(rawValue: rawValue, metadata) 22 | } 23 | return rawRepresentable 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcbaselines/17FB80FF1B530FED0012F106.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 78C19493-4A8B-4FE0-88D6-957F92628060 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 2300 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | MacBookPro10,1 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Johannes Lund 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Tests/Vehicle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Vehicle.swift 3 | // Decodable 4 | // 5 | // Created by Charlotte Tortorella on 12/04/2016. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import protocol Decodable.Decodable 10 | import enum Decodable.DecodingError 11 | @testable import Decodable 12 | 13 | protocol Vehicle { 14 | var driverless: Bool {get} 15 | } 16 | 17 | struct Car: Vehicle { 18 | let driverless: Bool 19 | } 20 | 21 | extension Car: Decodable { 22 | static func decode(_ json: Any) throws -> Car { 23 | return try Car(driverless: json => "driverless") 24 | } 25 | } 26 | 27 | struct Train: Vehicle { 28 | let driverless: Bool 29 | let electric: Bool 30 | } 31 | 32 | extension Train: Decodable { 33 | static func decode(_ json: Any) throws -> Train { 34 | return try Train(driverless: json => "driverless", 35 | electric: json => "electric") 36 | } 37 | } 38 | 39 | struct Truck: Vehicle { 40 | let driverless: Bool 41 | let wheels: UInt8 42 | } 43 | 44 | extension Truck: Decodable { 45 | static func decode(_ json: Any) throws -> Truck { 46 | return try Truck(driverless: json => "driverless", 47 | wheels: json => "wheels") 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Sources/KeyPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPath.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-07-09. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// `KeyPath` represents the path to a specific node in a tree of nested dictionaries. 12 | /// 13 | /// Can be created from string and array literals and can be joined by the `=>` operator. 14 | /// ``` 15 | /// let a: KeyPath = "a" 16 | /// let b: KeyPath = ["a", "b"] 17 | /// let c: KeyPath = "a" => "b" => "c" 18 | /// ``` 19 | 20 | public struct KeyPath { 21 | public var keys: [String] 22 | 23 | public init(_ keys: [String]) { 24 | self.keys = keys 25 | } 26 | 27 | public init(_ key: String) { 28 | self.keys = [key] 29 | } 30 | 31 | } 32 | 33 | extension KeyPath: ExpressibleByStringLiteral { 34 | public init(stringLiteral value: String) { 35 | self.keys = [value] 36 | } 37 | 38 | public init(extendedGraphemeClusterLiteral value: String) { 39 | self.keys = [value] 40 | } 41 | 42 | public init(unicodeScalarLiteral value: String) { 43 | self.keys = [value] 44 | } 45 | } 46 | 47 | extension KeyPath: ExpressibleByArrayLiteral { 48 | public init(arrayLiteral elements: String...) { 49 | self.keys = elements 50 | } 51 | } 52 | 53 | extension KeyPath: Equatable { } 54 | public func ==(lhs: KeyPath, rhs: KeyPath) -> Bool { 55 | return lhs.keys == rhs.keys 56 | } 57 | -------------------------------------------------------------------------------- /Tests/MissingKeyOperatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // missingKeyOperatorTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-12-20. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | import Decodable 13 | 14 | class missingKeyOperatorTests: XCTestCase { 15 | 16 | func testMissingKey() { 17 | // Should return nil 18 | let dictionary: NSDictionary = ["key": 3] 19 | let result: Int? = try! dictionary =>? "missingKeyError" 20 | XCTAssertEqual(result, nil) 21 | } 22 | 23 | func testNSNull() { 24 | // Should return nil 25 | let dictionary: NSDictionary = ["key": NSNull()] 26 | let result: Int? = try! dictionary =>? "key" 27 | XCTAssertEqual(result, nil) 28 | } 29 | 30 | func testSuccess() { 31 | let dictionary: NSDictionary = ["key": 3] 32 | let result: Int? = try! dictionary =>? "key" 33 | XCTAssertEqual(result, 3) 34 | } 35 | 36 | func testTypeMismatch() { 37 | // Should throw 38 | let dictionary: NSDictionary = ["key": "3"] 39 | do { 40 | let _: Int? = try dictionary =>? "key" 41 | XCTFail("should throw") 42 | } catch let DecodingError.typeMismatch(expected, _, _) { 43 | XCTAssert(expected == Int.self, "\(expected) != Int.self") 44 | } catch { 45 | XCTFail("Should not throw \(error)") 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Operators.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-07-08. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Operators 12 | 13 | precedencegroup DecodingPrecedence { 14 | associativity: right 15 | higherThan: CastingPrecedence 16 | } 17 | 18 | infix operator => : DecodingPrecedence 19 | infix operator =>? : DecodingPrecedence 20 | 21 | public func => (lhs: Any, rhs: KeyPath) throws -> Any { 22 | return try parse(lhs, keyPath: rhs, decoder: { $0 }) 23 | } 24 | 25 | 26 | public func =>? (lhs: Any, rhs: OptionalKeyPath) throws -> Any? { 27 | return try parse(lhs, keyPath: rhs, decoder: Optional.decoder({$0})) 28 | } 29 | 30 | // MARK: - JSONPath 31 | 32 | /// Enables parsing nested objects e.g json => "a" => "b" 33 | 34 | public func => (lhs: KeyPath, rhs: KeyPath) -> KeyPath { 35 | return KeyPath(lhs.keys + rhs.keys) 36 | } 37 | 38 | public func => (lhs: OptionalKeyPath, rhs: OptionalKeyPath) -> OptionalKeyPath { 39 | return OptionalKeyPath(keys: lhs.keys + rhs.markingFirst(required: true).keys) 40 | } 41 | 42 | public func =>? (lhs: OptionalKeyPath, rhs: OptionalKeyPath) -> OptionalKeyPath { 43 | return OptionalKeyPath(keys: lhs.keys + rhs.keys) 44 | } 45 | 46 | public func => (lhs: OptionalKeyPath, rhs: KeyPath) -> OptionalKeyPath { 47 | return OptionalKeyPath(keys: lhs.keys + rhs.keys.map { OptionalKey(key: $0, isRequired: true) }) 48 | } 49 | 50 | 51 | public func =>? (lhs: KeyPath, rhs: OptionalKeyPath) -> OptionalKeyPath { 52 | return OptionalKeyPath(keys: lhs.keys.map { OptionalKey(key: $0, isRequired: true) } + rhs.keys ) 53 | } 54 | -------------------------------------------------------------------------------- /Tests/DynamicDecodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicDecodableTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-07-29. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Foundation 11 | @testable import Decodable 12 | 13 | class DynamicDecodableTests: XCTestCase { 14 | 15 | private var originalBoolDecoder = Bool.decoder 16 | private var originalNSArrayDecoder = NSArray.decoder 17 | 18 | func testCustomBooleanDecoding() { 19 | Bool.decoder = { json in 20 | switch json { 21 | case let str as String where str == "true": 22 | return true 23 | case let str as String where str == "false": 24 | return false 25 | default: 26 | return try cast(json) 27 | } 28 | } 29 | let json: NSString = "true" 30 | let result = try! Bool.decode(json) 31 | XCTAssertEqual(result, true) 32 | 33 | Bool.decoder = originalBoolDecoder 34 | 35 | XCTAssertNil(try? Bool.decode(json)) 36 | } 37 | 38 | func testCustomArrayDecoding() { 39 | NSArray.decoder = { json in 40 | switch json { 41 | case let array as NSArray: 42 | return array 43 | default: 44 | return [json] 45 | } 46 | } 47 | 48 | let arrayJSON: NSArray = ["a", "b", "c"] 49 | let objectJSON: NSString = "d" 50 | 51 | XCTAssertEqual(try! [String].decode(arrayJSON), ["a", "b", "c"]) 52 | XCTAssertEqual(try! [String].decode(objectJSON), ["d"]) 53 | 54 | NSArray.decoder = originalNSArrayDecoder 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/DecodeAsOneOfTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodableTests.swift 3 | // DecodableTests 4 | // 5 | // Created by Johannes Lund on 2015-07-08. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | import struct Decodable.KeyPath 13 | @testable import Decodable 14 | 15 | class DecodeAsOneOfTests: XCTestCase { 16 | 17 | private func readJsonFile(_ file: String) -> NSDictionary { 18 | let filePath = (Bundle(for: object_getClass(self)!).resourcePath! as NSString).appendingPathComponent(file) 19 | print(filePath) 20 | let jsonString = try! String(contentsOfFile: filePath) 21 | let jsonData = jsonString.data(using: String.Encoding.utf8)! 22 | return try! JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary 23 | } 24 | 25 | func testDecodingSubtypesShouldSucceed() { 26 | // given 27 | let json = readJsonFile("Vehicle.json") 28 | 29 | // when 30 | do { 31 | 32 | let vehiclesRaw: Any = try json => "vehicles" 33 | 34 | let vehicles1 = try decodeArrayAsOneOf(vehiclesRaw, objectTypes: Train.self, Truck.self, Car.self) 35 | 36 | guard let vehiclesArray = vehiclesRaw as? [Any] else { 37 | let metadata = DecodingError.Metadata(object: vehiclesRaw) 38 | throw DecodingError.typeMismatch(expected: NSArray.self, actual: Mirror(reflecting: vehiclesRaw).subjectType, metadata) 39 | } 40 | 41 | let vehicles2 = try vehiclesArray.map { try decodeAsOneOf($0, objectTypes: Train.self, Truck.self, Car.self) } 42 | 43 | XCTAssertEqual(vehicles1.count, vehicles2.count) 44 | XCTAssertEqual(vehicles1.count, vehiclesArray.count) 45 | for truck in vehicles1.flatMap({ $0 as? Truck }) { 46 | XCTAssertEqual(truck.wheels, 18) 47 | } 48 | 49 | let train = vehicles1.flatMap { $0 as? Train }.first! 50 | XCTAssertEqual(train.electric, true) 51 | 52 | } catch { 53 | XCTFail("it should not throw an exception") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/RawRepresentableDecodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RawRepresentableDecodableTests.swift 3 | // Decodable 4 | // 5 | // Created by Daniel Garbień on 06/11/15. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | @testable import Decodable 13 | 14 | enum CMYKColor: String, Decodable { 15 | case Cyan = "Cyan" 16 | case Magenta = "Magenta" 17 | case Yellow = "Yellow" 18 | case Black = "Black" 19 | } 20 | 21 | class RawRepresentableDecodableTests: XCTestCase { 22 | 23 | func testDecodingCorrectRawRepresentableValueSucceed() { 24 | // given 25 | let key = "color" 26 | let color = "Cyan" 27 | let json: NSDictionary = [key: color] 28 | // when 29 | let cmykColor: CMYKColor = try! json => KeyPath(key) 30 | // then 31 | XCTAssertEqual(cmykColor, CMYKColor.Cyan) 32 | } 33 | 34 | func testDecodingIncorrectRawRepresentableValueFail() { 35 | // given 36 | let key = "color" 37 | let color = "Green" 38 | let json: NSDictionary = [key: color] 39 | // when 40 | do { 41 | _ = try json => KeyPath(key) as CMYKColor 42 | XCTFail() 43 | } catch DecodingError.rawRepresentableInitializationError(_, let metadata) { 44 | // then 45 | XCTAssertNotNil(metadata.object) 46 | } catch { 47 | XCTFail("should not throw \(error)") 48 | } 49 | } 50 | 51 | func testDecodingIncorrectRawRepresentableTypeFail() { 52 | // given 53 | let key = "color" 54 | let color = 0 55 | let json: NSDictionary = [key: color] 56 | // when 57 | do { 58 | _ = try json => KeyPath(key) as CMYKColor 59 | XCTFail() 60 | } catch let DecodingError.typeMismatch(expected, _, metadata) where expected == CMYKColor.RawValue.self { 61 | // then 62 | XCTAssertNotNil(metadata.object) 63 | } catch { 64 | XCTFail("should not throw \(error)") 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/ParseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-07-13. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Foundation 11 | import protocol Decodable.Decodable 12 | import enum Decodable.DecodingError 13 | import struct Decodable.KeyPath 14 | @testable import Decodable 15 | 16 | class ParseTests: XCTestCase { 17 | 18 | func testParseKeyPathSuccess() { 19 | let dict: NSDictionary = ["a": ["b": 3]] 20 | let a = try! parse(dict, ["a", "b"] as KeyPath) 21 | XCTAssertEqual(a as? Int, 3) 22 | } 23 | 24 | func testParseAndDecodeKeyPathSuccess() { 25 | let dict: NSDictionary = ["a": ["b": 3]] 26 | let a = try! parse(dict, keyPath: ["a", "b"], decoder: Int.decode) 27 | XCTAssertEqual(a, 3) 28 | } 29 | 30 | func testParseKeyPathMissingKey() { 31 | let dict: NSDictionary = ["a": ["b": 3]] 32 | do { _ = try parse(dict, KeyPath(["a", "c"])) } 33 | catch DecodingError.missingKey(let key, let metadata) { 34 | XCTAssertEqual(metadata.formattedPath, "a") 35 | XCTAssertEqual(key, "c") 36 | } catch { 37 | XCTFail("should not throw \(error)") 38 | } 39 | } 40 | 41 | func testParseAndDecodeKeyPathMissingKey() { 42 | let dict: NSDictionary = ["a": ["b": 3]] 43 | do { _ = try parse(dict, keyPath: ["a", "c"], decoder: Int.decode) } 44 | catch DecodingError.missingKey(let key, let metadata) { 45 | XCTAssertEqual(metadata.formattedPath, "a") 46 | XCTAssertEqual(key, "c") 47 | } catch { 48 | XCTFail("should not throw \(error)") 49 | } 50 | } 51 | 52 | func testParseAndDecodeKeyPathTypeMismatch() { 53 | let dict: NSDictionary = ["a": ["b": "3"]] 54 | do { _ = try parse(dict, keyPath: ["a", "b"], decoder: Int.decode) } 55 | catch DecodingError.typeMismatch(let expected, _, let metadata) { 56 | XCTAssertEqual("\(expected)", "Int") 57 | XCTAssertEqual(metadata.formattedPath, "a.b") 58 | } catch { 59 | XCTFail("should not throw \(error)") 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Sources/NSValueCastable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSValueCastable.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-01-06. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Int64: NSNumberCastable { 12 | public static func convertFrom(_ n: NSNumber) -> Int64 { return n.int64Value } 13 | } 14 | extension Int32: NSNumberCastable { 15 | public static func convertFrom(_ n: NSNumber) -> Int32 { return n.int32Value } 16 | } 17 | extension Int16: NSNumberCastable { 18 | public static func convertFrom(_ n: NSNumber) -> Int16 { return n.int16Value } 19 | } 20 | extension Int8: NSNumberCastable { 21 | public static func convertFrom(_ n: NSNumber) -> Int8 { return n.int8Value } 22 | } 23 | extension UInt64: NSNumberCastable { 24 | public static func convertFrom(_ n: NSNumber) -> UInt64 { return n.uint64Value } 25 | } 26 | extension UInt32: NSNumberCastable { 27 | public static func convertFrom(_ n: NSNumber) -> UInt32 { return n.uint32Value } 28 | } 29 | extension UInt16: NSNumberCastable { 30 | public static func convertFrom(_ n: NSNumber) -> UInt16 { return n.uint16Value } 31 | } 32 | extension UInt8: NSNumberCastable { 33 | public static func convertFrom(_ n: NSNumber) -> UInt8 { return n.uint8Value } 34 | } 35 | 36 | /// Provides a default implementation of decode() which casts the object to a NSValue and unsafely casts its value as Self. 37 | public protocol NSValueCastable: Decodable {} 38 | 39 | /// Used to enable decoding to different IntegerTypes from NSNumber. 40 | public protocol NSNumberCastable: NSValueCastable { 41 | static func convertFrom(_ n: NSNumber) -> Self 42 | } 43 | 44 | extension NSValueCastable { 45 | private typealias PointerOfSelf = UnsafeMutablePointer // Why do we have to do this? 46 | public static func decode(_ j: Any) throws -> Self { 47 | let value: NSValue = try cast(j) 48 | let pointer = PointerOfSelf.allocate(capacity: 1) 49 | defer { pointer.deallocate(capacity: 1) } 50 | value.getValue(pointer) 51 | return pointer.move() 52 | } 53 | } 54 | 55 | extension NSNumberCastable { 56 | public static func decode(_ json: Any) throws -> Self { 57 | return try convertFrom(cast(json)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcbaselines/8FE7B56B1B4C9FB900837609.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 2B184EB9-20A6-44F1-BFA7-F9185332D574 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 2300 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | MacBookPro10,1 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone8,1 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 73DA0DF5-ACA7-4948-824F-2F1739DC2034 39 | 40 | localComputer 41 | 42 | busSpeedInMHz 43 | 100 44 | cpuCount 45 | 1 46 | cpuKind 47 | Intel Core i7 48 | cpuSpeedInMHz 49 | 2300 50 | logicalCPUCoresPerPackage 51 | 8 52 | modelCode 53 | MacBookPro10,1 54 | physicalCPUCoresPerPackage 55 | 4 56 | platformIdentifier 57 | com.apple.platform.macosx 58 | 59 | targetArchitecture 60 | x86_64 61 | targetDevice 62 | 63 | modelCode 64 | iPhone7,2 65 | platformIdentifier 66 | com.apple.platform.iphonesimulator 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Sources/Decodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Decodable.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-07-07. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol Decodable { 12 | static func decode(_ json: Any) throws -> Self 13 | } 14 | 15 | 16 | extension Dictionary where Key: Decodable, Value: Decodable { 17 | public static func decode(_ j: Any) throws -> Dictionary { 18 | return try Dictionary.decoder(key: Key.decode, value: Value.decode)(j) 19 | } 20 | } 21 | 22 | /* FIXME: this causes ambiguity issues, in the meantime resort to `Dictionary.decoder` 23 | https://github.com/Anviking/Decodable/issues/120 24 | 25 | extension Dictionary where Key: Decodable, Value: Any { 26 | 27 | public static func decode(_ j: Any) throws -> Dictionary { 28 | let valueDecoder: (Any) throws -> Value = { try cast($0) } 29 | return try Dictionary.decoder(key: Key.decode, value: valueDecoder)(j) 30 | } 31 | } 32 | */ 33 | 34 | extension Array where Element: Decodable { 35 | public static func decode(_ j: Any, ignoreInvalidObjects: Bool = false) throws -> [Element] { 36 | if ignoreInvalidObjects { 37 | return try [Element?].decoder { try? Element.decode($0) }(j).flatMap {$0} 38 | } else { 39 | return try Array.decoder(Element.decode)(j) 40 | } 41 | } 42 | } 43 | 44 | 45 | 46 | 47 | // MARK: Helpers 48 | 49 | /// Attempt to decode one of multiple objects in order until: A: we get a positive match, B: we throw an exception if the last object does not decode 50 | public func decodeAsOneOf(_ json: Any, objectTypes: Decodable.Type...) throws -> Decodable { 51 | for decodable in objectTypes.dropLast() { 52 | if let decoded = try? decodable.decode(json) { 53 | return decoded 54 | } 55 | } 56 | return try objectTypes.last!.decode(json) 57 | } 58 | 59 | /// Attempt to decode one of multiple objects in order until: A: we get a positive match, B: we throw an exception if the last object does not decode 60 | public func decodeArrayAsOneOf(_ json: Any, objectTypes: Decodable.Type...) throws -> [Decodable] { 61 | return try NSArray.decode(json).map { 62 | for decodable in objectTypes.dropLast() { 63 | if let decoded = try? decodable.decode($0) { 64 | return decoded 65 | } 66 | } 67 | return try objectTypes.last!.decode($0) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/Repository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepositoryExample.swift 3 | // Decodable 4 | // 5 | // Created by Fran_DEV on 13/07/15. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | import struct Decodable.KeyPath 13 | @testable import Decodable 14 | 15 | struct Owner { 16 | let id: Int 17 | let login: String 18 | } 19 | 20 | struct Repository { 21 | let id: Int 22 | let name: String 23 | let description: String 24 | let htmlUrlString : String 25 | let owner: Owner // Struct conforming to Decodable 26 | let coverage: Double 27 | let files: Array 28 | let optional: String? 29 | let active: Bool 30 | let optionalActive: Bool? 31 | } 32 | 33 | extension Owner : Decodable { 34 | static func decode(_ j: Any) throws -> Owner { 35 | return try Owner( 36 | id: j => "id", 37 | login: j => "login" 38 | ) 39 | } 40 | } 41 | 42 | extension Repository : Decodable { 43 | static func decode(_ j: Any) throws -> Repository { 44 | return try Repository( 45 | id: j => "id", 46 | name: j => "name", 47 | description: j => "description", 48 | htmlUrlString : j => "html_url", 49 | owner: j => "owner", 50 | coverage: j => "coverage", 51 | files: j => "files", 52 | optional: j => "optional", 53 | active: j => "active", 54 | optionalActive: j => "optionalActive" 55 | ) 56 | } 57 | } 58 | 59 | // MARK: Equatable 60 | 61 | func == (lhs: Owner, rhs: Owner) -> Bool { 62 | return lhs.id == rhs.id && lhs.login == rhs.login 63 | } 64 | 65 | extension Owner: Equatable { 66 | var hashValue: Int { return id.hashValue } 67 | } 68 | 69 | func == (lhs: Repository, rhs: Repository) -> Bool { 70 | return lhs.id == rhs.id && 71 | lhs.name == rhs.name && 72 | lhs.description == rhs.description && 73 | lhs.htmlUrlString == rhs.htmlUrlString && 74 | lhs.owner == rhs.owner && 75 | lhs.coverage == rhs.coverage && 76 | lhs.files == rhs.files && 77 | lhs.optional == rhs.optional && 78 | lhs.active == rhs.active && 79 | lhs.optionalActive == rhs.optionalActive 80 | } 81 | 82 | extension Repository: Equatable { 83 | var hashValue: Int { return id.hashValue } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/NSValueDecodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSValueDecodableTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-01-06. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Decodable 11 | 12 | class NSValueDecodableTests: XCTestCase { 13 | func testIntegerDecodingFromInt64() { 14 | let number = NSNumber(value: 100) 15 | XCTAssertEqual(try! Int64.decode(number), number.int64Value) 16 | XCTAssertEqual(try! Int32.decode(number), number.int32Value) 17 | XCTAssertEqual(try! Int16.decode(number), number.int16Value) 18 | XCTAssertEqual(try! Int8.decode(number), number.int8Value) 19 | 20 | XCTAssertEqual(try! UInt64.decode(number), number.uint64Value) 21 | XCTAssertEqual(try! UInt32.decode(number), number.uint32Value) 22 | XCTAssertEqual(try! UInt16.decode(number), number.uint16Value) 23 | XCTAssertEqual(try! UInt8.decode(number), number.uint8Value) 24 | } 25 | 26 | func testIntegerDecodingFromInt32() { 27 | let number = NSNumber(value: 100) 28 | XCTAssertEqual(try! Int64.decode(number), number.int64Value) 29 | XCTAssertEqual(try! Int32.decode(number), number.int32Value) 30 | XCTAssertEqual(try! Int16.decode(number), number.int16Value) 31 | XCTAssertEqual(try! Int8.decode(number), number.int8Value) 32 | 33 | XCTAssertEqual(try! UInt64.decode(number), number.uint64Value) 34 | XCTAssertEqual(try! UInt32.decode(number), number.uint32Value) 35 | XCTAssertEqual(try! UInt16.decode(number), number.uint16Value) 36 | XCTAssertEqual(try! UInt8.decode(number), number.uint8Value) 37 | } 38 | 39 | func testIntegerDecodingFromInt8() { 40 | let number = NSNumber(value: 100) 41 | 42 | XCTAssertEqual(try! Int64.decode(number), number.int64Value) 43 | XCTAssertEqual(try! Int32.decode(number), number.int32Value) 44 | XCTAssertEqual(try! Int16.decode(number), number.int16Value) 45 | XCTAssertEqual(try! Int8.decode(number), number.int8Value) 46 | 47 | XCTAssertEqual(try! UInt64.decode(number), number.uint64Value) 48 | XCTAssertEqual(try! UInt32.decode(number), number.uint32Value) 49 | XCTAssertEqual(try! UInt16.decode(number), number.uint16Value) 50 | XCTAssertEqual(try! UInt8.decode(number), number.uint8Value) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Decoders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Closure.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-07-10. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Optional { 12 | 13 | /// Creates an optional decoder from a decoder of the Wrapped type 14 | /// 15 | /// This function is used by `=>` and `=>?` overloads when decoding `T?` 16 | /// 17 | /// - parameter wrappedDecoder: A decoder (decode closure) for the wrapped type 18 | /// - returns: A closure takes an JSON object, checks it's `NSNull`, if so returns `nil`, otherwise calls the wrapped decode closure. 19 | static func decoder(_ wrappedDecoder: @escaping (Any) throws -> Wrapped) -> (Any) throws -> Wrapped? { 20 | return { json in 21 | if json is NSNull { 22 | return nil 23 | } else { 24 | return try wrappedDecoder(json) 25 | } 26 | } 27 | } 28 | } 29 | 30 | extension Array { 31 | 32 | /// Creates an array decoder from an element decoder 33 | /// 34 | /// This function is used by `=>` and `=>?` overloads when decoding `[T]` 35 | /// 36 | /// - parameter elementDecoder: A decoder (decode closure) for the `Element` type 37 | /// - throws: if `NSArray.decode` throws or any element decode closure throws 38 | /// - returns: A closure that takes an `NSArray` and maps it using the element decode closure 39 | public static func decoder(_ elementDecoder: @escaping (Any) throws -> Element) -> (Any) throws -> Array { 40 | return { json in 41 | return try NSArray.decode(json).map { try elementDecoder($0) } 42 | } 43 | } 44 | } 45 | 46 | extension Dictionary { 47 | /// Create a dictionary decoder from key- and value- decoders 48 | /// 49 | /// This function is used by `=>` and `=>?` overloads when decoding `[K: V]` 50 | /// 51 | /// - parameter key: A decoder (decode closure) for the `Key` type 52 | /// - parameter value: A decoder (decode closure) for the `Value` type 53 | /// - returns: A closure that takes a `NSDictionary` and "maps" it using key and value decode closures 54 | public static func decoder(key keyDecoder: @escaping (Any) throws -> Key, value valueDecoder: @escaping (Any) throws -> Value) -> (Any) throws -> Dictionary { 55 | return { json in 56 | var dict = Dictionary() 57 | for (key, value) in try NSDictionary.decode(json) { 58 | try dict[keyDecoder(key)] = valueDecoder(value) 59 | } 60 | return dict 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Tests/DictionaryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-10-30. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | import Decodable 13 | 14 | private struct Color: Decodable, Equatable { 15 | let name: String 16 | static func decode(_ json: Any) throws -> Color { 17 | return try Color(name: String.decode(json)) 18 | } 19 | 20 | static func == (lhs: Color, rhs: Color) -> Bool { 21 | return lhs.name == rhs.name 22 | } 23 | 24 | } 25 | 26 | 27 | 28 | /* FIXME: https://github.com/Anviking/Decodable/issues/120 29 | private struct AccessibilityInfo: Decodable { 30 | let data: [String: Any] 31 | 32 | static func decode(_ json: Any) throws -> AccessibilityInfo { 33 | return try AccessibilityInfo( 34 | data: [String: Any].decode(json) 35 | ) 36 | } 37 | } 38 | */ 39 | class DictionaryTests: XCTestCase { 40 | 41 | override func setUp() { 42 | super.setUp() 43 | // Put setup code here. This method is called before the invocation of each test method in the class. 44 | } 45 | 46 | override func tearDown() { 47 | // Put teardown code here. This method is called after the invocation of each test method in the class. 48 | super.tearDown() 49 | } 50 | 51 | func testNormal() { 52 | let json: NSDictionary = ["object": ["key1": 1, "key2": 2, "key3": 3]] 53 | let a: [String: Int] = try! json => "object" 54 | XCTAssertEqual(a, ["key1": 1, "key2": 2, "key3": 3]) 55 | } 56 | 57 | func testDictionaryWithDecodableValues() { 58 | let json: NSDictionary = ["r": "red", "g": "green"] 59 | let result = try! [String: Color].decode(json) 60 | XCTAssertEqual(["r": Color(name: "red"), "g": Color(name: "green")], result) 61 | } 62 | 63 | func testOptionalReturn() { 64 | var json: NSDictionary = ["object": ["key1": 1, "key2": 2, "key3": 3]] 65 | var result: [String: Int]? = try! json => "object" 66 | XCTAssertEqual(result!, ["key1": 1, "key2": 2, "key3": 3]) 67 | 68 | json = ["object": NSNull()] 69 | result = try! json => "object" 70 | XCTAssertNil(result) 71 | } 72 | 73 | /* FIXME: https://github.com/Anviking/Decodable/issues/120 74 | func testStringAny() { 75 | let dict: NSDictionary = ["a": 2] 76 | let info = try! AccessibilityInfo.decode(dict) 77 | XCTAssertEqual(info.data as NSDictionary, dict) 78 | 79 | } 80 | */ 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Sources/OptionalKeyPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalKeyPath.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-07-09. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A key in a keyPath that may or may not be required. 12 | /// 13 | /// Trying to access a invalid key in a dictionary with an `OptionalKey` will usually result 14 | /// in a nil return value instead of a thrown error. Unless `isRequired` is `true`, in which 15 | /// it behaves as a "normal" `String` inside a "normal" `KeyPath`. 16 | public struct OptionalKey { 17 | public var key: String 18 | public var isRequired: Bool 19 | } 20 | 21 | extension OptionalKey: CustomStringConvertible { 22 | public var description: String { 23 | return key + (isRequired ? "" : "?") 24 | } 25 | } 26 | 27 | /// `OptionalKeyPath` represents the path to a specific node in a tree of nested dictionaries. 28 | /// 29 | /// Can be created from string and array literals and can be joined by the `=>?` operator. 30 | /// ``` 31 | /// let a: OptionalKeyPath = "a" 32 | /// let b: OptionalKeyPath = ["a", "b"] 33 | /// let c: OptionalKeyPath = "a" =>? "b" =>? "c" 34 | /// ``` 35 | /// Unlike `KeyPath`, `OptionalKeyPath` allows each key to be either required or optional. 36 | ///`isRequired` is `false` by default. 37 | /// 38 | /// When a `KeyPath` is converted to a OptionalKeyPath, `isRequired` is set to `true`. 39 | /// ``` 40 | /// let c: OptionalKeyPath = "a" =>? "b" => "c" 41 | /// ^^ 42 | /// isRequired=true 43 | /// ``` 44 | /// In the above example `"c"` is inferred as `KeyPath`, then converted to `OptionalKeyPath` 45 | /// with `isRequired = true` 46 | 47 | public struct OptionalKeyPath { 48 | public var keys: [OptionalKey] 49 | mutating func markFirst(required: Bool) { 50 | if var first = keys.first { 51 | first.isRequired = required 52 | keys[0] = first 53 | } 54 | } 55 | 56 | func markingFirst(required: Bool) -> OptionalKeyPath { 57 | var new = self 58 | if var first = keys.first { 59 | first.isRequired = required 60 | new.keys[0] = first 61 | } 62 | return new 63 | } 64 | } 65 | 66 | extension OptionalKeyPath: ExpressibleByStringLiteral { 67 | public init(stringLiteral value: String) { 68 | self.keys = [OptionalKey(key: value, isRequired: false)] 69 | } 70 | 71 | public init(extendedGraphemeClusterLiteral value: String) { 72 | self.keys = [OptionalKey(key: value, isRequired: false)] 73 | } 74 | 75 | public init(unicodeScalarLiteral value: String) { 76 | self.keys = [OptionalKey(key: value, isRequired: false)] 77 | } 78 | } 79 | 80 | extension OptionalKeyPath: ExpressibleByArrayLiteral { 81 | public init(arrayLiteral elements: String...) { 82 | self.keys = elements.map { OptionalKey(key: $0, isRequired: false) } 83 | } 84 | } 85 | 86 | // MARK: Equality 87 | 88 | extension OptionalKey: Equatable {} 89 | public func == (lhs: OptionalKey, rhs: OptionalKey) -> Bool { 90 | return lhs.key == rhs.key && lhs.isRequired == rhs.isRequired 91 | } 92 | -------------------------------------------------------------------------------- /Sources/Parse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parse.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-08-13. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func parse(_ json: Any, _ keyPath: KeyPath) throws -> Any { 12 | var currentDict = json 13 | 14 | for (index, key) in keyPath.keys.enumerated() { 15 | guard let result = try NSDictionary.decode(currentDict)[key] else { 16 | let currentPath = keyPath.keys[0 ..< index] 17 | let metadata = DecodingError.Metadata(path: Array(currentPath), object: currentDict, rootObject: json) 18 | throw DecodingError.missingKey(key, metadata) 19 | } 20 | 21 | currentDict = result 22 | } 23 | 24 | return currentDict 25 | } 26 | 27 | func parse(_ json: Any, _ path: OptionalKeyPath) throws -> Any? { 28 | var currentDict = json 29 | 30 | for (index, key) in path.keys.enumerated() { 31 | guard let result = try NSDictionary.decode(currentDict)[key.key] else { 32 | if key.isRequired { 33 | let currentPath = path.keys[0 ..< index].map { $0.key } 34 | let metadata = DecodingError.Metadata(path: currentPath, object: currentDict, rootObject: json) 35 | throw DecodingError.missingKey(key.key, metadata) 36 | } else { 37 | return nil 38 | } 39 | } 40 | currentDict = result 41 | } 42 | 43 | return currentDict 44 | } 45 | public func parse(_ json: Any, keyPath: KeyPath, decoder: ((Any) throws -> T)) throws -> T { 46 | let object = try parse(json, keyPath) 47 | return try catchAndRethrow(json, keyPath) { try decoder(object) } 48 | } 49 | 50 | // FIXME: Should perhaps not return T?, but this way we don't have to flatMap in certain overloads 51 | public func parse(_ json: Any, keyPath: OptionalKeyPath, decoder: ((Any) throws -> T?)) throws -> T? { 52 | guard let object = try parse(json, keyPath) else { return nil } 53 | return try catchAndRethrow(json, keyPath) { try decoder(object) } 54 | } 55 | 56 | 57 | // MARK: - Helpers 58 | 59 | func catchMissingKeyAndReturnNil(_ closure: () throws -> T) throws -> T? { 60 | do { 61 | return try closure() 62 | } catch DecodingError.missingKey { 63 | return nil 64 | } 65 | } 66 | 67 | func catchAndRethrow(_ json: Any, _ keyPath: KeyPath, block: () throws -> T) throws -> T { 68 | do { 69 | return try block() 70 | } catch let error as DecodingError { 71 | var error = error 72 | error.metadata.path = keyPath.keys + error.metadata.path 73 | error.metadata.rootObject = json 74 | throw error 75 | } catch let error { 76 | throw error 77 | } 78 | } 79 | 80 | func catchAndRethrow(_ json: Any, _ keyPath: OptionalKeyPath, block: () throws -> T) throws -> T { 81 | do { 82 | return try block() 83 | } catch let error as DecodingError { 84 | var error = error 85 | error.metadata.path = keyPath.keys.map{$0.key} + error.metadata.path 86 | error.metadata.rootObject = json 87 | throw error 88 | } catch let error { 89 | throw error 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcschemes/Decodable-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcschemes/Decodable-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Tests/KeyPathTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPathTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2016-07-09. 6 | // Copyright © 2016 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | import struct Decodable.KeyPath 13 | @testable import Decodable 14 | class KeyPathTests: XCTestCase { 15 | 16 | func testCreateFromStringLiteral() { 17 | let keyPath: KeyPath = "a" 18 | XCTAssertEqual(keyPath.keys, ["a"]) 19 | } 20 | 21 | func testCreateFromArrayLiteral() { 22 | let keyPath: KeyPath = ["a", "b"] 23 | XCTAssertEqual(keyPath.keys, ["a", "b"]) 24 | } 25 | 26 | func testCreateWithOperators() { 27 | let keyPath: KeyPath = "a" => "b" 28 | XCTAssertEqual(keyPath.keys, ["a", "b"]) 29 | } 30 | 31 | func testJoiningKeyPaths() { 32 | let a: KeyPath = "a" 33 | let bAndC: KeyPath = ["b", "c"] 34 | let keyPath: KeyPath = a => bAndC 35 | XCTAssertEqual(keyPath.keys, ["a", "b", "c"]) 36 | } 37 | } 38 | 39 | class OptionalKeyPathTests: XCTestCase { 40 | func testCreateFromStringLiteral() { 41 | let keyPath: OptionalKeyPath = "a" 42 | XCTAssertEqual(keyPath.keys, [OptionalKey(key: "a", isRequired: false)]) 43 | } 44 | 45 | func testCreateFromArrayLiteral() { 46 | let keyPath: OptionalKeyPath = ["a", "b"] 47 | XCTAssertEqual(keyPath.keys, [ 48 | OptionalKey(key: "a", isRequired: false), 49 | OptionalKey(key: "b", isRequired: false), 50 | ]) 51 | } 52 | 53 | func testCreateWithOperators() { 54 | let keyPath: OptionalKeyPath = "a" =>? "b" 55 | XCTAssertEqual(keyPath.keys, [ 56 | OptionalKey(key: "a", isRequired: false), 57 | OptionalKey(key: "b", isRequired: false), 58 | ]) 59 | } 60 | 61 | func testJoiningKeyPaths() { 62 | let a: OptionalKeyPath = "a" 63 | let bAndC: OptionalKeyPath = ["b", "c"] 64 | let keyPath: OptionalKeyPath = a =>? bAndC 65 | XCTAssertEqual(keyPath.keys, [ 66 | OptionalKey(key: "a", isRequired: false), 67 | OptionalKey(key: "b", isRequired: false), 68 | OptionalKey(key: "c", isRequired: false) 69 | ]) 70 | } 71 | 72 | // MARK: More difficult ones 73 | 74 | 75 | func testConversionFromKeyPath() { 76 | let keyPath1: OptionalKeyPath = "a" => "b" => "c" 77 | XCTAssertEqual(keyPath1.keys, [ 78 | OptionalKey(key: "a", isRequired: false), 79 | OptionalKey(key: "b", isRequired: true), 80 | OptionalKey(key: "c", isRequired: true) 81 | ]) 82 | 83 | let keyPath2: OptionalKeyPath = "a" =>? "b" => "c" 84 | XCTAssertEqual(keyPath2.keys, [ 85 | OptionalKey(key: "a", isRequired: false), 86 | OptionalKey(key: "b", isRequired: false), 87 | OptionalKey(key: "c", isRequired: true) 88 | ]) 89 | 90 | let keyPath3: OptionalKeyPath = "a" => "b" =>? "c" 91 | XCTAssertEqual(keyPath3.keys, [ 92 | OptionalKey(key: "a", isRequired: false), 93 | OptionalKey(key: "b", isRequired: true), 94 | OptionalKey(key: "c", isRequired: false) 95 | ]) 96 | 97 | let keyPath4: OptionalKeyPath = KeyPath("a") =>? "b" =>? "c" 98 | XCTAssertEqual(keyPath4.keys, [ 99 | OptionalKey(key: "a", isRequired: true), 100 | OptionalKey(key: "b", isRequired: false), 101 | OptionalKey(key: "c", isRequired: false) 102 | ]) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/Castable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Castable.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-09-25. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Attempt to cast an `Any` to `T` or throw 12 | /// 13 | /// - throws: `DecodingError.typeMismatch(expected, actual, metadata)` 14 | public func cast(_ object: Any) throws -> T { 15 | guard let result = object as? T else { 16 | let metadata = DecodingError.Metadata(object: object) 17 | throw DecodingError.typeMismatch(expected: T.self, actual: type(of: object), metadata) 18 | } 19 | return result 20 | } 21 | 22 | /// Allows overriding default `decode` function from your app. 23 | /// 24 | /// You likely don't want to conform to this yourself. 25 | public protocol DynamicDecodable { 26 | associatedtype DecodedType 27 | 28 | /// A closure describing how this type should be decoded 29 | /// 30 | /// Types also conforming to `Decodable` call this closure 31 | /// from their `decode` function. 32 | /// 33 | /// - note: This is intended as a set-once thing. 34 | static var decoder: (Any) throws -> DecodedType {get set} 35 | } 36 | 37 | extension Decodable where Self: DynamicDecodable, Self.DecodedType == Self { 38 | public static func decode(_ json: Any) throws -> Self { 39 | return try decoder(json) 40 | 41 | } 42 | } 43 | 44 | extension String: Decodable, DynamicDecodable { 45 | public static var decoder: (Any) throws -> String = { try cast($0) } 46 | } 47 | extension Int: Decodable, DynamicDecodable { 48 | public static var decoder: (Any) throws -> Int = { try cast($0) } 49 | } 50 | extension Double: Decodable, DynamicDecodable { 51 | public static var decoder: (Any) throws -> Double = { try cast($0) } 52 | } 53 | extension Bool: Decodable, DynamicDecodable { 54 | public static var decoder: (Any) throws -> Bool = { try cast($0) } 55 | } 56 | 57 | private let iso8601DateFormatter: DateFormatter = { 58 | let formatter = DateFormatter() 59 | formatter.locale = Locale(identifier: "en_US_POSIX") 60 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" 61 | return formatter 62 | }() 63 | 64 | extension Date: Decodable, DynamicDecodable { 65 | /// Default decoder is `Date.decoder(using: iso8601DateFormatter)` 66 | public static var decoder: (Any) throws -> Date = Date.decoder(using: iso8601DateFormatter) 67 | 68 | /// Create a decode closure using a given formatter 69 | /// 70 | /// Example usage: 71 | /// ``` 72 | /// let formatter = DateFormatter(...) 73 | /// Date.decoder = Date.decoder(using: formatter) 74 | /// ``` 75 | public static func decoder(using formatter: DateFormatter) -> (Any) throws -> Date { 76 | return { object in 77 | let string = try String.decode(object) 78 | guard let date = formatter.date(from: string) else { 79 | let metadata = DecodingError.Metadata(object: object) 80 | throw DecodingError.rawRepresentableInitializationError(rawValue: string, metadata) 81 | } 82 | return date 83 | } 84 | } 85 | 86 | } 87 | 88 | extension NSDictionary: Decodable { 89 | public static func decode(_ json: Any) throws -> Self { 90 | return try cast(json) 91 | } 92 | } 93 | 94 | extension NSArray: DynamicDecodable { 95 | public static var decoder: (Any) throws -> NSArray = { try cast($0) } 96 | public static func decode(_ json: Any) throws -> NSArray { 97 | return try decoder(json) 98 | } 99 | 100 | } 101 | 102 | 103 | extension URL: DynamicDecodable, Decodable { 104 | public static var decoder: (Any) throws -> URL = { object in 105 | let string = try String.decode(object) 106 | guard let url = URL(string: string) else { 107 | let metadata = DecodingError.Metadata(object: object) 108 | throw DecodingError.rawRepresentableInitializationError(rawValue: string, metadata) 109 | } 110 | return url 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Tests/ErrorPathTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorPathTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-07-16. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | @testable import Decodable 13 | 14 | private struct Color: Decodable { 15 | let name: String 16 | 17 | static func decode(_ json: Any) throws -> Color { 18 | return try Color(name: json => "name") 19 | } 20 | } 21 | 22 | private struct Apple: Decodable { 23 | let id: Int 24 | let color: Color? 25 | 26 | static func decode(_ json: Any) throws -> Apple { 27 | return try Apple(id: json => "id", color: json => "color") 28 | } 29 | } 30 | 31 | private struct Tree: Decodable { 32 | let apples: [Apple] 33 | 34 | static func decode(_ json: Any) throws -> Tree { 35 | return try Tree(apples: json => "apples") 36 | } 37 | } 38 | 39 | class ErrorPathTests: XCTestCase { 40 | 41 | func testMissingKeyErrorPath() { 42 | 43 | let dict: NSDictionary = ["object": ["repo": ["owner": ["id" : 1, "login": "anviking"]]]] 44 | 45 | do { 46 | _ = try dict => "object" => "repo" => "owner" => "oops" as String 47 | } catch DecodingError.missingKey(_ , let metadata) { 48 | XCTAssertEqual(metadata.formattedPath, "object.repo.owner") 49 | } catch let error { 50 | XCTFail("should not throw this exception: \(error)") 51 | } 52 | } 53 | 54 | // FIXME: # 55 | func testNestedUnexpectedNSNull() { 56 | let dict: NSDictionary = ["id": 1, "color": ["name": NSNull()]] 57 | do { 58 | let apple = try Apple.decode(dict) 59 | print(apple) 60 | XCTFail() 61 | } catch DecodingError.typeMismatch(_, _, let metadata) where metadata.object is NSNull { 62 | 63 | } catch let error { 64 | XCTFail("should not throw this exception: \(error)") 65 | } 66 | } 67 | 68 | func testTypeMismatchErrorPath() { 69 | 70 | let dict: NSDictionary = ["object": ["repo": ["owner": ["id" : 1, "login": 0]]]] 71 | 72 | do { 73 | _ = try dict => "object" => "repo" => "owner" => "login" as String 74 | } catch let DecodingError.typeMismatch(_, actual, metadata) { 75 | let typeString = String(describing: actual) 76 | XCTAssertTrue(typeString.contains("Number"), "\(typeString) should contain NSNumber") 77 | XCTAssertEqual(metadata.formattedPath, "object.repo.owner.login") 78 | XCTAssertEqual(metadata.object as? Int, 0) 79 | } catch let error { 80 | XCTFail("should not throw this exception: \(error)") 81 | } 82 | } 83 | 84 | func testNestedObjectTypeMismatchPath() { 85 | let dict: NSDictionary = ["apples": [["id": 2, "color": ["name": "red"]], 86 | ["id": 2, "color": ["name": "green"]], 87 | ["id": 2, "color": ["name": 3]]]] 88 | do { 89 | _ = try Tree.decode(dict) 90 | XCTFail() 91 | } catch let DecodingError.typeMismatch(_, actual, metadata) { 92 | XCTAssertTrue(String(describing: actual).contains("Number")) 93 | XCTAssertEqual(metadata.formattedPath, "apples.color.name") 94 | } catch let error { 95 | XCTFail("should not throw this exception: \(error)") 96 | } 97 | } 98 | 99 | 100 | func testFoo() { 101 | let dictionary: NSDictionary = ["key": ["test": 3]] 102 | let a: Int = try! uppercase(dictionary => "key") => "TEST" 103 | XCTAssertEqual(a, 3) 104 | } 105 | 106 | private func uppercase(_ json: NSDictionary) -> NSDictionary { 107 | var result = [String: Any]() 108 | for (key, value) in json { 109 | result[(key as! String).uppercased()] = value 110 | } 111 | print(result) 112 | return result as NSDictionary 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcschemes/Decodable-Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/xcshareddata/xcschemes/Decodable-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Tests/DecodableExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodableExtensionTests.swift 3 | // Decodable 4 | // 5 | // Created by FJBelchi on 13/07/15. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | @testable import Decodable 13 | 14 | class DecodableExtensionTests: XCTestCase { 15 | 16 | // MARK: String 17 | func testStringDecodableSuccess() { 18 | //given 19 | let anyObject = "hello" 20 | //when 21 | let string = try! String.decode(anyObject) 22 | //then 23 | XCTAssertEqual(string, anyObject) 24 | } 25 | 26 | func testStringDecodableFail() { 27 | //given 28 | let anyObject = 0 29 | //when 30 | do { 31 | _ = try String.decode(anyObject) 32 | } catch DecodingError.typeMismatch { 33 | //then 34 | XCTAssertTrue(true) 35 | } catch { 36 | XCTFail("should not throw this exception") 37 | } 38 | } 39 | 40 | // MARK: Int 41 | func testIntDecodable() { 42 | //given 43 | let anyObject = 0 44 | //when 45 | let int = try! Int.decode(anyObject) 46 | //then 47 | XCTAssertEqual(int, anyObject) 48 | } 49 | 50 | func testIntDecodableFail() { 51 | //given 52 | let anyObject = "" 53 | //when 54 | do { 55 | _ = try Int.decode(anyObject) 56 | } catch DecodingError.typeMismatch { 57 | //then 58 | XCTAssertTrue(true) 59 | } catch { 60 | XCTFail("should not throw this exception") 61 | } 62 | } 63 | 64 | // MARK: Double 65 | func testDoubleDecodable() { 66 | //given 67 | let anyObject = 0.5 68 | //when 69 | let double = try! Double.decode(anyObject) 70 | //then 71 | XCTAssertEqual(double, anyObject) 72 | } 73 | 74 | func testDoubleDecodableFail() { 75 | //given 76 | let anyObject = "" 77 | //when 78 | do { 79 | _ = try Double.decode(anyObject) 80 | } catch DecodingError.typeMismatch { 81 | //then 82 | XCTAssertTrue(true) 83 | } catch { 84 | XCTFail("should not throw this exception") 85 | } 86 | } 87 | 88 | // MARK: Bool 89 | func testBoolDecodable() { 90 | //given 91 | let anyObject = true 92 | //when 93 | let bool = try! Bool.decode(anyObject) 94 | //then 95 | XCTAssertEqual(bool, anyObject) 96 | } 97 | 98 | func testBoolDecodableFail() { 99 | //given 100 | let anyObject = "" 101 | //when 102 | do { 103 | _ = try Bool.decode(anyObject) 104 | } catch DecodingError.typeMismatch { 105 | //then 106 | XCTAssertTrue(true) 107 | } catch { 108 | XCTFail("should not throw this exception") 109 | } 110 | } 111 | 112 | // MARK: Date 113 | func testDateDecodable() { 114 | //given 115 | let anyObject = "1970-01-01T00:00:00Z" 116 | //when 117 | let date = try! Date.decode(anyObject) 118 | //then 119 | XCTAssertEqual(date, Date(timeIntervalSince1970: 0)) 120 | } 121 | 122 | func testDateDecodableFail() { 123 | //given 124 | let anyObject = "" 125 | //when 126 | do { 127 | _ = try Date.decode(anyObject) 128 | } catch DecodingError.rawRepresentableInitializationError(let rawValue, let metaData) { 129 | //then 130 | XCTAssertEqual(rawValue as! String, "") 131 | XCTAssertEqual(metaData.object as! String, anyObject) 132 | } catch { 133 | XCTFail("should not throw this exception") 134 | } 135 | } 136 | 137 | // MARK: URL 138 | func testURLDecodable() { 139 | //given 140 | let anyObject = "http://www.google.com" 141 | //when 142 | let url = try! URL.decode(anyObject) 143 | //then 144 | XCTAssertEqual(url, URL(string: anyObject)) 145 | } 146 | 147 | func testURLDecodableFail() { 148 | //given 149 | let anyObject = "" 150 | //when 151 | do { 152 | _ = try URL.decode(anyObject) 153 | } catch DecodingError.rawRepresentableInitializationError(let rawValue, let metaData) { 154 | //then 155 | XCTAssertEqual(rawValue as! String, "") 156 | XCTAssertEqual(metaData.object as! String, anyObject) 157 | } catch { 158 | XCTFail("should not throw this exception") 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Sources/DecodingError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodingError.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-07-17. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum DecodingError: Error, Equatable { 12 | 13 | /// `DecodingError.Metadata` provides information about 14 | /// where an `DecodingError` was thrown in the JSON 15 | /// object graph. 16 | public struct Metadata: Equatable { 17 | 18 | public init(path: [String] = [], object: Any, rootObject: Any? = nil) { 19 | self.path = path 20 | self.object = object 21 | self.rootObject = rootObject 22 | } 23 | 24 | /// The JSON key path to the object that failed to be decoded 25 | public var path: [String] 26 | 27 | /// The JSON object that failed to be decoded 28 | public let object: Any 29 | 30 | /// The root JSON object for which the `path` can be used to find `object` 31 | public var rootObject: Any? 32 | 33 | /// Represents the path to the object that failed decoding with "." as a separator. 34 | public var formattedPath: String { 35 | return path.joined(separator: ".") 36 | } 37 | } 38 | 39 | /// Thrown when optional casting from `Any` fails. 40 | /// 41 | /// This can happen both when trying to access a key on a object 42 | /// that isn't a `NSDictionary`, and failing to cast a `Castable` 43 | /// primitive. 44 | case typeMismatch(expected: Any.Type, actual: Any.Type, Metadata) 45 | 46 | /// Thrown when a given, required, key was not found in a dictionary. 47 | case missingKey(String, Metadata) 48 | 49 | /// Thrown from the `RawRepresentable` extension when 50 | /// `init(rawValue:)` returned `nil`. 51 | case rawRepresentableInitializationError(rawValue: Any, Metadata) 52 | 53 | /// When an error is thrown that isn't `DecodingError`, it 54 | /// will be wrapped in `DecodingError.other` in order to also provide 55 | /// metadata about where the error was thrown. 56 | case other(Error, Metadata) 57 | 58 | public var metadata: Metadata { 59 | get { 60 | switch self { 61 | case .typeMismatch(expected: _, actual: _, let metadata): 62 | return metadata 63 | case .missingKey(_, let metadata): 64 | return metadata 65 | case .rawRepresentableInitializationError(_, let metadata): 66 | return metadata 67 | case .other(_, let metadata): 68 | return metadata 69 | } 70 | } 71 | 72 | set { 73 | switch self { 74 | case let .typeMismatch(expected, actual, _): 75 | self = .typeMismatch(expected: expected, actual: actual, newValue) 76 | case let .missingKey(key, _): 77 | self = .missingKey(key, newValue) 78 | case let .rawRepresentableInitializationError(rawValue, _): 79 | self = DecodingError.rawRepresentableInitializationError(rawValue: rawValue, newValue) 80 | case let .other(error, _): 81 | self = .other(error, newValue) 82 | } 83 | } 84 | 85 | } 86 | 87 | public var debugDescription: String { 88 | switch self { 89 | case let .typeMismatch(expected, actual, metadata): 90 | return "typeMismatch expected: \(expected) but \(metadata.object) is of type \(actual) in \(metadata.formattedPath)" 91 | case let .missingKey(key, metadata): 92 | return "missingKey \(key) in \(metadata.formattedPath) \(metadata.object)" 93 | case let .rawRepresentableInitializationError(rawValue, metadata): 94 | return "rawRepresentableInitializationError: \(rawValue) could not be used to initialize \("TYPE"). (path: \(metadata.formattedPath))" // FIXME 95 | case let .other(error, _): 96 | return "\(error)" 97 | } 98 | } 99 | 100 | } 101 | 102 | 103 | // Allow types to be used in pattern matching 104 | // E.g case typeMismatchError(NSNull.self, _, _) but be careful 105 | // You probably rather want to modify the decode-closure 106 | // There are overloads for this 107 | public func ~=(lhs: T.Type, rhs: Any.Type) -> Bool { 108 | return lhs == rhs 109 | } 110 | 111 | // FIXME: I'm not sure about === equality 112 | public func ==(lhs: DecodingError.Metadata, rhs: DecodingError.Metadata) -> Bool { 113 | return lhs.object as AnyObject === rhs.object as AnyObject 114 | && lhs.path == rhs.path 115 | && lhs.rootObject as AnyObject === rhs.rootObject as AnyObject 116 | } 117 | 118 | public func ==(lhs: DecodingError, rhs: DecodingError) -> Bool { 119 | switch (lhs, rhs) { 120 | case let (.typeMismatch(expected, actual, metadata), .typeMismatch(expected2, actual2, metadata2)): 121 | return expected == expected2 122 | && actual == actual2 123 | && metadata == metadata2 124 | case let (.missingKey(key, metadata), .missingKey(key2, metadata2)): 125 | return key == key2 126 | && metadata == metadata2 127 | case let (.rawRepresentableInitializationError(rawValue, metadata), .rawRepresentableInitializationError(rawValue2, metadata2)): 128 | // FIXME: Might be strange 129 | switch (rawValue, rawValue2, metadata == metadata2) { 130 | case let (a as AnyObject, b as AnyObject, true): 131 | return a === b 132 | default: 133 | return false 134 | } 135 | case (.other, .other): 136 | // FIXME: What to do? 137 | print("FIXME: other equality is unimplemented/not supported") 138 | return false 139 | default: 140 | return false 141 | } 142 | } 143 | 144 | -------------------------------------------------------------------------------- /Tests/ArrayTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayTests.swift 3 | // Decodable 4 | // 5 | // Created by Johannes Lund on 2015-07-19. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | import struct Decodable.KeyPath 13 | @testable import Decodable 14 | 15 | class DecodableArrayTests: XCTestCase { 16 | 17 | func testDecodeAnyDecodableArraySuccess() { 18 | // given 19 | let key = "key" 20 | let value: NSArray = ["value1", "value2", "value3"] 21 | let dictionary: NSDictionary = [key: value] 22 | // when 23 | let result = try! dictionary => KeyPath(key) as Array 24 | // then 25 | XCTAssertEqual(result, value as! [String]) 26 | } 27 | 28 | func testDecodeOptionalDecodableArraySuccess() { 29 | // given 30 | let key = "key" 31 | let value: NSArray = ["value1", "value2", NSNull(), "value3"] 32 | let dictionary: NSDictionary = [key: value] 33 | // when 34 | let result = try! dictionary => KeyPath(key) as [String?] 35 | // then 36 | XCTAssertEqual(result.count, 4) 37 | XCTAssertEqual(result[0], "value1") 38 | XCTAssertEqual(result[1], "value2") 39 | XCTAssertEqual(result[2], nil) 40 | XCTAssertEqual(result[3], "value3") 41 | } 42 | 43 | func testDecodeOptionalDecodableArrayFailure() { 44 | // given 45 | let key = "key" 46 | let value: NSArray = ["value1", "value2", 0x8BAD, "value3"] 47 | let dictionary: NSDictionary = [key: value] 48 | // when 49 | do { 50 | _ = try dictionary => KeyPath(key) as [String?] 51 | XCTFail("should throw") 52 | } catch DecodingError.typeMismatch { 53 | // Yay 54 | } catch { 55 | XCTFail("should not throw \(error)") 56 | } 57 | } 58 | 59 | func testDecodeNestedDecodableArraySuccess() { 60 | // given 61 | let value: NSArray = ["value1", "value2", "value3"] 62 | let dictionary: NSDictionary = ["key": ["key": value]] 63 | // when 64 | let result = try! dictionary => "key" => "key" as Array 65 | // then 66 | XCTAssertEqual(result, value as! [String]) 67 | } 68 | 69 | func testDecodeAnyDecodableOptionalArraySuccess() { 70 | // given 71 | let key = "key" 72 | let value = ["value"] 73 | let dictionary: NSDictionary = [key: value] 74 | // when 75 | let string = try! dictionary => KeyPath(key) as [String]? 76 | // then 77 | XCTAssertEqual(string!, value) 78 | } 79 | 80 | func testDecodeAnyDecodableNestedOptionalArraySuccess() { 81 | // given 82 | let value = ["value"] 83 | let dictionary: NSDictionary = ["key": ["key": value]] 84 | // when 85 | let string = try! dictionary => "key" => "key" as [String]? 86 | // then 87 | XCTAssertEqual(string!, value) 88 | } 89 | 90 | func testDecodeAnyDecodableOptionalArrayNilSuccess() { 91 | // given 92 | let key = "key" 93 | let dictionary: NSDictionary = [key: NSNull()] 94 | // when 95 | let string = try! dictionary => KeyPath(key) as [String]? 96 | // then 97 | XCTAssertNil(string) 98 | } 99 | 100 | func testDecodeAnyDecodableOptionalArrayMissingKeyFailure() { 101 | // given 102 | let key = "key" 103 | let dictionary = NSDictionary() 104 | // when 105 | do { 106 | _ = try dictionary => KeyPath(key) as [String]? 107 | XCTFail() 108 | } catch DecodingError.missingKey { 109 | 110 | } catch { 111 | XCTFail() 112 | } 113 | } 114 | 115 | 116 | // MARK: =>? 117 | 118 | func testDecodeSafeArraySuccess() { 119 | // given 120 | let key = "key" 121 | let value = ["A", "B", "C"] 122 | let dictionary: NSDictionary = [key: value] 123 | // when 124 | let array = try? [String].decode(dictionary => "key") 125 | // then 126 | XCTAssertEqual(array!, value) 127 | } 128 | 129 | func testDecodeSafeArrayCatchTypeExceptionMismatch() { 130 | // given 131 | let key = "key" 132 | let value = ["A", 2, "B"] as [Any] 133 | let dictionary: NSDictionary = [key: value] 134 | // when 135 | let array = try! [String].decode(dictionary => "key", ignoreInvalidObjects: true) 136 | // then 137 | XCTAssertEqual(array, ["A", "B"]) 138 | } 139 | 140 | func testDecodeSafeArrayCatchTypeMismatchExceptionInObjects() { 141 | // given 142 | let key = "key" 143 | let value = [["id": "007", "login": "mradams"], ["id": 1, "login": "jenglish"]] 144 | let dictionary: NSDictionary = [key: value] 145 | // when 146 | let array = try! [Owner].decode(dictionary => "key", ignoreInvalidObjects: true) 147 | // then 148 | XCTAssertEqual(array, [Owner(id: 1, login: "jenglish")]) 149 | } 150 | 151 | func testDecodeSafeArrayCatchJSONNotObjectException() { 152 | // given 153 | let key = "key" 154 | let value = [["id": 7, "login": "mradams"], 2] as [Any] 155 | let dictionary: NSDictionary = [key: value] 156 | // when 157 | let array = try! [Owner].decode(dictionary => "key", ignoreInvalidObjects: true) 158 | // then 159 | XCTAssertEqual(array, [Owner(id: 7, login: "mradams")]) 160 | } 161 | 162 | func testDecodeSafeArrayCatchMissingKeyException() { 163 | // given 164 | let key = "key" 165 | let value = [["login": "mradams"], ["id": 1, "login": "jenglish"]] 166 | let dictionary: NSDictionary = [key: value] 167 | // when 168 | let array = try! [Owner].decode(dictionary => KeyPath(key), ignoreInvalidObjects: true) 169 | // then 170 | XCTAssertEqual(array, [Owner(id: 1, login: "jenglish")]) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Tests/DecodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodableTests.swift 3 | // DecodableTests 4 | // 5 | // Created by Johannes Lund on 2015-07-08. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | import struct Decodable.KeyPath 13 | @testable import Decodable 14 | 15 | class DecodableTests: XCTestCase { 16 | 17 | private func readJsonFile(_ file: String) -> NSDictionary { 18 | let filePath = (Bundle(for: object_getClass(self)!).resourcePath! as NSString).appendingPathComponent(file) 19 | let jsonString = try! String(contentsOfFile: filePath) 20 | let jsonData = jsonString.data(using: String.Encoding.utf8)! 21 | return try! JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary 22 | } 23 | 24 | func testDecodeRepositoryExampleShouldSuccess() { 25 | // given 26 | let json = readJsonFile("Repository.json") 27 | 28 | // when 29 | do { 30 | let repository = try Repository.decode(json) 31 | // then 32 | XCTAssertEqual(repository.id, json["id"] as? Int) 33 | XCTAssertEqual(repository.name, json["name"] as? String) 34 | XCTAssertEqual(repository.description, json["description"] as? String) 35 | XCTAssertEqual(repository.htmlUrlString, json["html_url"] as? String) 36 | 37 | let owner = repository.owner 38 | let ownerDictionary = json["owner"] as! NSDictionary 39 | XCTAssertEqual(owner.id, ownerDictionary["id"] as? Int) 40 | XCTAssertEqual(owner.login, ownerDictionary["login"] as? String) 41 | 42 | XCTAssertEqual(repository.coverage, json["coverage"] as? Double) 43 | let files = repository.files 44 | XCTAssertEqual(files.count, 3) 45 | let array = json["files"] as! Array 46 | XCTAssertEqual(files[0], array[0]) 47 | XCTAssertEqual(files[1], array[1]) 48 | XCTAssertEqual(files[2], array[2]) 49 | XCTAssertNil(repository.optional) 50 | XCTAssertTrue(repository.active) 51 | XCTAssertNil(repository.optionalActive) 52 | } catch { 53 | XCTFail("it should not throw an exception") 54 | } 55 | } 56 | 57 | private let Count = 500 58 | 59 | func testDecodeArrayOfRepositoriesAndMeasureTime() { 60 | let json = readJsonFile("Repository.json") 61 | let array = NSArray(array: Array(repeating: json, count: Count)) 62 | 63 | var result: [Repository] = [] 64 | measure { 65 | do { 66 | result = try [Repository].decode(array) 67 | } catch let error { 68 | XCTFail("\(error)") 69 | } 70 | } 71 | XCTAssertEqual(result.count, Count) 72 | } 73 | 74 | func testCustomParseAndMeasureTime() { 75 | let json = readJsonFile("Repository.json") 76 | let array = NSArray(array: Array(repeating: json, count: Count)) 77 | 78 | var result: [Repository] = [] 79 | measure { 80 | do { 81 | result = try self.customParseRepository(array) 82 | } catch let error { 83 | XCTFail("\(error)") 84 | } 85 | } 86 | XCTAssertEqual(result.count, Count) 87 | 88 | } 89 | 90 | private func customParseRepository(_ json: Any) throws -> [Repository] { 91 | let error = NSError(domain: "test", code: 0, userInfo: nil) 92 | guard let array = json as? [NSDictionary] else { 93 | throw error 94 | } 95 | var result: [Repository] = [] 96 | for dict in array { 97 | guard let id = dict["id"] as? Int, 98 | let name = dict["name"] as? String, 99 | let description = dict["description"] as? String, 100 | let htmlUrlString = dict["html_url"] as? String, 101 | let userDict = dict["owner"] as? NSDictionary, 102 | let ownerID = userDict["id"] as? Int, 103 | let ownerLogin = userDict["login"] as? String, 104 | let coverage = dict["coverage"] as? Double, 105 | let files = dict["files"] as? NSArray as? [String], 106 | let active = dict["active"] as? Bool 107 | else { 108 | throw error 109 | } 110 | let optional = dict["optional"] as? String 111 | let optionalActive = dict["optionalActive"] as? Bool 112 | 113 | let owner = Owner(id: ownerID, login: ownerLogin) 114 | let repo = Repository( 115 | id: id, 116 | name: name, 117 | description: description, 118 | htmlUrlString : htmlUrlString, 119 | owner: owner, 120 | coverage: coverage, 121 | files: files, 122 | optional: optional, 123 | active: active, 124 | optionalActive: optionalActive) 125 | result.append(repo) 126 | } 127 | return result 128 | } 129 | 130 | func testDecodeRepositoryExampleShouldThrowMissingKeyException() { 131 | // given 132 | let json = readJsonFile("missingKey.json") 133 | 134 | // when 135 | do { 136 | _ = try Repository.decode(json) 137 | } catch DecodingError.missingKey(let key, _) { 138 | // then 139 | XCTAssertEqual(key, "id") 140 | } catch let error as DecodingError { 141 | XCTFail("it should not throw \(error)") 142 | } catch { 143 | XCTFail("it should not throw this exception") 144 | } 145 | } 146 | 147 | func testDecodeRepositoryExampleShouldThrowTypeMismatchException() { 148 | // given 149 | let json = readJsonFile("typeMismatch.json") 150 | 151 | // when 152 | do { 153 | _ = try Repository.decode(json) 154 | } catch DecodingError.missingKey { 155 | XCTFail("it should not throw this exception") 156 | } catch let DecodingError.typeMismatch(expected, _, metadata) where expected == Int.self { 157 | // then 158 | XCTAssertEqual(metadata.formattedPath, "id") 159 | } catch let error { 160 | XCTFail("should not throw \(error)") 161 | } 162 | } 163 | 164 | func testDecodeRepositoryExampleNestedShouldThrowTypeMismatchException() { 165 | // given 166 | let json: NSDictionary = ["key": readJsonFile("typeMismatch.json")] 167 | 168 | // when 169 | do { 170 | _ = (try parse(json, keyPath: ["key"], decoder: Repository.decode)) as Repository 171 | } catch DecodingError.missingKey { 172 | XCTFail("it should not throw this exception") 173 | } catch let DecodingError.typeMismatch(expected, _, metadata) where expected == Int.self { 174 | // then 175 | XCTAssertEqual(metadata.formattedPath, "key.id") 176 | } catch let error { 177 | XCTFail("should not throw \(error)") 178 | } 179 | } 180 | 181 | func testDecodeRepositoryExampleShouldThrowNoJsonObjectException() { 182 | // given 183 | let filePath = (Bundle(for: object_getClass(self)!).resourcePath! as NSString).appendingPathComponent("NoJsonObject.json") 184 | let jsonString = try! String(contentsOfFile: filePath) 185 | 186 | // when 187 | do { 188 | _ = try Repository.decode(jsonString) 189 | } catch DecodingError.missingKey { 190 | XCTFail("it should not throw this exception") 191 | } catch let DecodingError.typeMismatch(expected, _, metadata) where expected == NSDictionary.self { 192 | XCTAssertEqual(metadata.path, []) 193 | XCTAssertNotNil(metadata.object) 194 | } catch { 195 | XCTFail("should not throw \(error)") 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decodable 2 | Simple and strict, yet powerful object mapping made possible by Swift 2's error handling. Greatly inspired by [Argo](http://github.com/thoughtbot/Argo), but without a bizillion functional operators. 3 | 4 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![Cocoapods version](https://cocoapod-badges.herokuapp.com/v/Decodable/badge.png)](https://cocoapods.org/pods/Decodable) 6 | [![Platforms](https://cocoapod-badges.herokuapp.com/p/Decodable/badge.png)](https://cocoadocs.org/docsets/NSStringMask) 7 | [![Travis](https://img.shields.io/travis/Anviking/Decodable/master.svg)](https://travis-ci.org/Anviking/Decodable/branches) 8 | 9 | 10 | ```swift 11 | 12 | struct Repository { 13 | let name: String 14 | let description: String 15 | let stargazersCount: Int 16 | let language: String? 17 | let sometimesMissingKey: String? 18 | 19 | let owner: User // Struct conforming to Decodable 20 | let defaultBranch: Branch // Struct NOT conforming to Decodable 21 | 22 | var fullName: String { return "\(owner.login)/\(name)" } 23 | } 24 | 25 | extension Repository: Decodable { 26 | static func decode(j: Any) throws -> Repository { 27 | return try Repository( 28 | name: j => "nested" => "name", 29 | description: j => "description", 30 | stargazersCount: j => "stargazers_count", 31 | language: j => "language", 32 | sometimesMissingKey: j =>? "sometimesMissingKey", 33 | owner: j => "owner", 34 | defaultBranch: Branch(name: j => "default_branch") 35 | ) 36 | } 37 | } 38 | 39 | do { 40 | let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) 41 | let repo = try [Repository].decode(json) 42 | } catch { 43 | print(error) 44 | } 45 | ``` 46 | 47 | ## How does it work? 48 | 49 | ### A protocol 50 | ```swift 51 | public protocol Decodable { 52 | static func decode(json: Any) throws -> Self 53 | } 54 | ``` 55 | ### A parse-function 56 | ```swift 57 | public func parse(json: Any, path: [String], decode: (Any throws -> T)) throws -> T 58 | ``` 59 | 60 | ### And shameless operator-overloading 61 | The too-many generated overloads, all calling the `parse`-function, can be found in [Overloads.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Overloads.swift). Return types include `T?`, `[T?]`, `[T?]?`, `Any` and `[String: T]?`. When conditional protocol conformance is supported in Swift this won't be necessary, and automagic decoding of infinitly nested generic types (like `[[[[[[[[[A???]]: B]]]?]]?]]`) would work. 62 | 63 | An overload may look like this: 64 | ```swift 65 | public func => (json: Any, keyPath: KeyPath) throws -> T 66 | ``` 67 | 68 | ## KeyPaths 69 | Keypaths can be created from string and array literals as well as with explicit initializers. They can also be joined using the operators `=>` and `=>?`. `=>?` is another operator that indicates that `nil` should be returned if the key to the right is missing. 70 | 71 | - When composing `=>` and `=>?` operators in the same keypath, the strictness of `=>` is still honoured. 72 | - Optional key paths (`=>?`) require an optional return type 73 | 74 | ```swift 75 | let a: KeyPath = "a" 76 | let b: KeyPath = ["a", "b"] 77 | let c: KeyPath = "a" => "b" => "c" 78 | let string: String? = json =>? "key1" => "key2" => "key3"` 79 | ^^^^ allowed to be missing 80 | ``` 81 | ## Errors 82 | Errors will be caught and rethrown in the decoding process to backpropagate metadata, like the JSON object that failed decoding, the key path to it, and the root JSON object. 83 | 84 | From [DecodingError.swift](https://github.com/anviking/decodable/tree/master/Sources/DecodingError.swift): 85 | ```swift 86 | public enum DecodingError: ErrorProtocol, Equatable { 87 | /// Thrown when optional casting from `Any` fails. 88 | /// 89 | /// This can happen both when trying to access a key on a object 90 | /// that isn't a `NSDictionary`, and failing to cast a `Castable` 91 | /// primitive. 92 | case typeMismatch(expected: Any.Type, actual: Any.Type, Metadata) 93 | 94 | /// Thrown when a given, required, key was not found in a dictionary. 95 | case missingKey(String, Metadata) 96 | 97 | /// Thrown from the `RawRepresentable` extension when 98 | /// `init(rawValue:)` returned `nil`. 99 | case rawRepresentableInitializationError(rawValue: Any, Metadata) 100 | 101 | /// When an error is thrown that isn't `DecodingError`, it 102 | /// will be wrapped in `DecodingError.other` in order to also provide 103 | /// metadata about where the error was thrown. 104 | case other(ErrorProtocol, Metadata) 105 | } 106 | ``` 107 | 108 | ```swift 109 | let dict: NSDictionary = ["object": ["repo": ["owner": ["id" : 1, "login": "anviking"]]]] 110 | 111 | do { 112 | let username: String = try dict => "object" => "repo" => "owner" => "name" 113 | } catch let error { 114 | print(error) 115 | } 116 | // 117 | // MissingKeyError at object.repo.owner: name in { 118 | // id = 1; 119 | // login = anviking; 120 | // } 121 | ``` 122 | 123 | ## Handling Errors 124 | Expressions like `j => "key"` will throw directly, and `catch`-statements can be used to create the most complex error handling behaviours. This also means that `try?` can be used to return nil if *anything* goes wrong instead of throwing. 125 | 126 | For convenience there is an operator, `=>?`, that only returns nil on missing keys, for APIs that indicate `null` in that manner, and to aid working with different response formats. 127 | 128 | | Overload | Null Behaviour | Missing Key Behavior |Type Mismatch Behaviour | Errors in subobjects | 129 | | ------------- |:-------------:|:-----:|:-----:|:-----:| 130 | | `=> -> T`| throws | throws | throws | uncaught (throws) | 131 | | `=> -> T?`| nil | throws | throws | uncaught (throws) | 132 | | `=>? -> T?`| nil | nil | throws | uncaught (throws) | 133 | | `try? => -> T `| nil | nil | nil | caught (nil) | 134 | 135 | ## Customization 136 | `Int`, `Double`,`String`, `Bool`, `Date` (ISO8601), `NSArray`, and `NSDictionary` types that conform to `DynamicDecodable` with the following declaration: 137 | ```swift 138 | public protocol DynamicDecodable { 139 | associatedtype DecodedType 140 | static var decoder: (Any) throws -> DecodedType {get set} 141 | } 142 | ``` 143 | This allows Decodable to implement default decoding closures while allowing you to override them as needed. 144 | ```swift 145 | // Lets extend Bool.decoder so that it accepts certain strings: 146 | Bool.decoder = { json in 147 | switch json { 148 | case let str as String where str == "true": 149 | return true 150 | case let str as String where str == "false": 151 | return false 152 | default: 153 | return try cast(json) 154 | } 155 | } 156 | ``` 157 | 158 | Note that when extending new types to conform to `Decodable` there is really no point in conforming to `DynamicDecodable` since you already control the implementation. Also note that the `decoder` properties are intended as "set once". If you need different behaviour on different occations, please create custom decode functions. 159 | 160 | The default `Date.decoder` uses a ISO8601 date formatter. If you don't want to create your own decode closure there's a helper: 161 | ```swift 162 | Date.decoder = Date.decoder(using: formatter) 163 | ``` 164 | 165 | ## When `Decodable` isn't enough 166 | Don't be afraid of not conforming to `Decodable`. 167 | ```swift 168 | let array = try NSArray.decode(json => "list").map { 169 | try Contribution(json: $0, repository: repo) 170 | } 171 | ``` 172 | 173 | ## Tips 174 | - You can use `Decodable` with classes. Just make sure to either call a `required` initializer on self (e.g `self.init`) and return `Self`, or make your class `final`. ( [This](http://stackoverflow.com/questions/26495586/best-practice-to-implement-a-failable-initializer-in-swift) might be a problem though) 175 | - The `Decodable`-protocol and the `=>`-operator should in no way make you committed to use them everywhere. 176 | 177 | ## Compatibility 178 | 179 | | Swift version | Compatible tag or branch | 180 | | --- | --- | 181 | | Swift 4.0 | `0.6.0` | 182 | | Swift 3.0 | `v0.5` | 183 | | Swift 2.3 | `v0.4.4`| 184 | | Swift 2.2 | `v0.4.3`| 185 | 186 | ## Note on Swift 4.0 usage 187 | Due to collisions with the standard library you will have to import ambiguous symbols specifically, in addition to `Decodable` as a whole. 188 | 189 | This means you likely want the following 190 | ```swift 191 | import Decodable 192 | import protocol Decodable.Decodable 193 | ``` 194 | and you can import other symbols, e.g `KeyPath`, `DecodingError`, in a simlilar fashion (using `import struct` and `import enum`) 195 | -------------------------------------------------------------------------------- /Generator/Generator.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/xcrun --sdk macosx swift 2 | 3 | // 4 | // Generator.swift 5 | // Decodable 6 | // 7 | // Created by Johannes Lund on 2016-02-27. 8 | // Copyright © 2016 anviking. All rights reserved. 9 | // 10 | 11 | // For generating overloads 12 | 13 | import Foundation 14 | 15 | // ---------------------------------------------------------------------------------------- 16 | // MARK: Documentation 17 | // ---------------------------------------------------------------------------------------- 18 | 19 | struct Behaviour { 20 | let throwsIfKeyMissing: Bool 21 | let throwsIfNull: Bool 22 | let throwsFromDecodeClosure: Bool 23 | } 24 | 25 | 26 | let fileManager = FileManager.default 27 | let documentationTemplate = try String(contentsOfFile: fileManager.currentDirectoryPath + "/Templates/Documentation.swift") 28 | 29 | func documentationFromTemplate(path: String, throwsIf: String, returns: String) -> String { 30 | return documentationTemplate 31 | .replacingOccurrences(of: "{path}", with: path) 32 | .replacingOccurrences(of: "{throws}", with: throwsIf) 33 | .replacingOccurrences(of: "{returns}", with: returns) 34 | } 35 | 36 | func generateDocumentationComment(_ behaviour: Behaviour) -> String { 37 | switch (behaviour.throwsIfKeyMissing, behaviour.throwsIfNull) { 38 | case (true, true): 39 | return documentationFromTemplate( 40 | path: "`KeyPath`– can be appended using with `=>` or `=>?`", 41 | throwsIf: "`DecodingError.typeMismatchError`,`.other(error, metadata)` or possible `.missingKeyError` on required keys", 42 | returns: "something" 43 | ) 44 | case (true, false): 45 | return documentationFromTemplate( 46 | path: "`KeyPath`– can be appended using with `=>` or `=>?`", 47 | throwsIf: "`DecodingError` if a key is missing or decoding fails.", 48 | returns: "`nil` if the object at `path` is `NSNull`" 49 | ) 50 | case (false, false): 51 | return documentationFromTemplate( 52 | path: "`KeyPath`– can be appended using with `=>` or `=>?`", 53 | throwsIf: "`DecodingError.typeMismatch, `.other(error, metadata)` or possible `.missingKeyError` on required keys", 54 | returns: "`nil` if the object at `path` is `NSNull` or if any optional key is missing." 55 | ) 56 | case (false, true): 57 | fatalError("This case should never happen, right?") 58 | } 59 | } 60 | 61 | // ---------------------------------------------------------------------------------------- 62 | // MARK: 63 | // ---------------------------------------------------------------------------------------- 64 | 65 | 66 | class TypeNameProvider { 67 | var names = Array(["A", "B", "C", "D", "E", "F", "G"].reversed()) 68 | var takenNames: [Unique: String] = [:] 69 | subscript(key: Unique) -> String { 70 | if let name = takenNames[key] { 71 | return name 72 | } 73 | 74 | let n = names.popLast()! 75 | takenNames[key] = n 76 | return n 77 | } 78 | 79 | } 80 | 81 | struct Unique: Hashable, Equatable { 82 | static var counter = 0 83 | let value: Int 84 | init() { 85 | Unique.counter += 1 86 | value = Unique.counter 87 | } 88 | var hashValue: Int { 89 | return value.hashValue 90 | } 91 | } 92 | 93 | func == (a: Unique, b: Unique) -> Bool { 94 | return a.value == b.value 95 | } 96 | 97 | indirect enum Decodable { 98 | case T(Unique) 99 | // case AnyObject 100 | case Array(Decodable) 101 | case Optional(Decodable) 102 | case Dictionary(Decodable, Decodable) 103 | 104 | func decodeClosure(_ provider: TypeNameProvider) -> String { 105 | switch self { 106 | case .T(let key): 107 | return "\(provider[key]).decode" 108 | // case .AnyObject: 109 | // return "{$0}" 110 | case .Optional(let T): 111 | return "Optional.decoder(\(T.decodeClosure(provider)))" 112 | case .Array(let T): 113 | return "Array.decoder(\(T.decodeClosure(provider)))" 114 | case .Dictionary(let K, let T): 115 | return "Dictionary.decoder(key: \(K.decodeClosure(provider)), value: \(T.decodeClosure(provider)))" 116 | } 117 | } 118 | 119 | func typeString(_ provider: TypeNameProvider) -> String { 120 | switch self { 121 | case .T(let unique): 122 | return provider[unique] 123 | case .Optional(let T): 124 | return "\(T.typeString(provider))?" 125 | case .Array(let T): 126 | return "[\(T.typeString(provider))]" 127 | case .Dictionary(let K, let T): 128 | return "[\(K.typeString(provider)): \(T.typeString(provider))]" 129 | } 130 | } 131 | 132 | func generateAllPossibleChildren(_ deepness: Int) -> [Decodable] { 133 | guard deepness > 0 else { return [.T(Unique())] } 134 | 135 | var array = [Decodable]() 136 | array += generateAllPossibleChildren(deepness - 1).flatMap(filterChainedOptionals) 137 | array += generateAllPossibleChildren(deepness - 1).map { .Array($0) } 138 | array += generateAllPossibleChildren(deepness - 1).map { .Dictionary(.T(Unique()),$0) } 139 | array += [.T(Unique())] 140 | return array 141 | } 142 | 143 | func wrapInOptionalIfNeeded() -> Decodable { 144 | switch self { 145 | case .Optional: 146 | return self 147 | default: 148 | return .Optional(self) 149 | } 150 | } 151 | 152 | var isOptional: Bool { 153 | switch self { 154 | case .Optional: 155 | return true 156 | default: 157 | return false 158 | } 159 | } 160 | 161 | func generateOverloads(_ operatorString: String) -> [String] { 162 | let provider = TypeNameProvider() 163 | let behaviour: Behaviour 164 | let keyPathType: String 165 | 166 | let returnType = typeString(provider) 167 | let overloads = [String]() 168 | 169 | let arguments = provider.takenNames.values.sorted().map { $0 + ": Decodable" } 170 | let generics = arguments.count > 0 ? "<\(arguments.joined(separator: ", "))>" : "" 171 | 172 | switch operatorString { 173 | case "=>": 174 | behaviour = Behaviour(throwsIfKeyMissing: true, throwsIfNull: !isOptional, throwsFromDecodeClosure: true) 175 | keyPathType = "KeyPath" 176 | 177 | /* 178 | // Start again 179 | guard isOptional else { break } 180 | let otherBehaviour = Behaviour(throwsIfKeyMissing: false, throwsIfNull: !isOptional, throwsFromDecodeClosure: true) 181 | let documentation = generateDocumentationComment(otherBehaviour) 182 | overloads.append(documentation + "public func \(operatorString) \(generics)(json: AnyObject, keyPath: OptionalKeyPath) throws -> \(returnType) {\n" + 183 | " return try parse(json, keyPath: keyPath.markingFirst(required: true), decode: \(decodeClosure(provider)))\n" + 184 | "}" 185 | ) 186 | */ 187 | 188 | case "=>?": 189 | //returnType += "?" 190 | // Never trows if null 191 | behaviour = Behaviour(throwsIfKeyMissing: false, throwsIfNull: false, throwsFromDecodeClosure: true) 192 | keyPathType = "OptionalKeyPath" 193 | default: 194 | fatalError() 195 | } 196 | 197 | let documentation = generateDocumentationComment(behaviour) 198 | return overloads + [documentation + "public func \(operatorString) \(generics)(json: Any, keyPath: \(keyPathType)) throws -> \(returnType) {\n" + 199 | " return try parse(json, keyPath: keyPath, decoder: \(decodeClosure(provider)))\n" + 200 | "}" 201 | ] 202 | } 203 | } 204 | 205 | func filterChainedOptionals(type: Decodable) -> Decodable? { 206 | switch type { 207 | case .Optional: 208 | return nil 209 | default: 210 | return .Optional(type) 211 | } 212 | } 213 | 214 | func filterOptionals(type: Decodable) -> Decodable? { 215 | switch type { 216 | case .Optional: 217 | return nil 218 | default: 219 | return type 220 | } 221 | } 222 | 223 | 224 | let file = "Overloads.swift" 225 | let sourcesDirectory = fileManager.currentDirectoryPath + "/../Sources" 226 | 227 | 228 | let filename = "Overloads.swift" 229 | let path = sourcesDirectory + "/" + filename 230 | 231 | var dateFormatter = DateFormatter() 232 | dateFormatter.dateStyle = .short 233 | 234 | let date = dateFormatter.string(from: Date()) 235 | 236 | let overloads = Decodable.T(Unique()).generateAllPossibleChildren(4) 237 | let types = overloads.map { $0.typeString(TypeNameProvider()) } 238 | let all = overloads.flatMap { $0.generateOverloads("=>") } + overloads.flatMap(filterOptionals).map{ $0.wrapInOptionalIfNeeded() }.flatMap { $0.generateOverloads("=>?") } 239 | 240 | do { 241 | var template = try String(contentsOfFile: fileManager.currentDirectoryPath + "/Templates/Header.swift") 242 | template = template.replacingOccurrences(of: "{filename}", with: filename) 243 | template = template.replacingOccurrences(of: "{by}", with: "Generator.swift") 244 | template = template.replacingOccurrences(of: "{overloads}", with: types.joined(separator: ", ")) 245 | template = template.replacingOccurrences(of: "{count}", with: "\(all.count)") 246 | let text = (template as String) + "\n" + all.joined(separator: "\n\n") 247 | try text.write(toFile: sourcesDirectory + "/Overloads.swift", atomically: false, encoding: String.Encoding.utf8) 248 | } 249 | catch { 250 | print(error) 251 | } 252 | -------------------------------------------------------------------------------- /Tests/DecodableOperatorsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodableOperatorsTests.swift 3 | // Decodable 4 | // 5 | // Created by FJBelchi on 13/07/15. 6 | // Copyright © 2015 anviking. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import protocol Decodable.Decodable 11 | import enum Decodable.DecodingError 12 | @testable import Decodable 13 | 14 | class DecodableOperatorsTests: XCTestCase { 15 | 16 | // MARK: => 17 | 18 | func testDecodeAnyDecodableSuccess() { 19 | // given 20 | let key = "key" 21 | let value = "value" 22 | let dictionary: NSDictionary = [key: value] 23 | // when 24 | let string = try! dictionary => KeyPath(key) as String 25 | // then 26 | XCTAssertEqual(string, value) 27 | } 28 | 29 | func testDecodeAnyDecodableDictionarySuccess() { 30 | // given 31 | let key = "key" 32 | let value: NSDictionary = [key : "value"] 33 | let dictionary: NSDictionary = [key: value] 34 | // when 35 | let result: NSDictionary = try! dictionary => KeyPath(key) as! NSDictionary 36 | // then 37 | XCTAssertEqual(result, value) 38 | } 39 | 40 | func testDecodeDictOfArraysSucess() { 41 | // given 42 | let key = "key" 43 | let value: NSDictionary = ["list": [1, 2, 3]] 44 | let dictionary: NSDictionary = [key: value] 45 | // when 46 | let result: [String: [Int]] = try! dictionary => KeyPath(key) 47 | // then 48 | XCTAssertEqual(result as NSDictionary, value) 49 | } 50 | 51 | // MARK: - Nested keys 52 | 53 | func testDecodeNestedDictionarySuccess() { 54 | // given 55 | let value: NSDictionary = ["aKey" : "value"] 56 | let dictionary: NSDictionary = ["key": ["key": value]] 57 | // when 58 | let result = try! dictionary => "key" => "key" 59 | // then 60 | XCTAssertEqual(result as? NSDictionary, value) 61 | } 62 | 63 | func testDecodeNestedDictionaryOptionalSuccess() { 64 | // given 65 | let value: NSDictionary = ["aKey" : "value"] 66 | let dictionary: NSDictionary = ["key": ["key": value]] 67 | // when 68 | let result = try! dictionary => "key" => "key" as! [String : Any] 69 | // then 70 | XCTAssertEqual(result as NSDictionary, value) 71 | } 72 | 73 | // TODO: this does not compile with Swift 3 74 | // func testDecodeNestedIntSuccess() { 75 | // // given 76 | // let value = 4 77 | // let dictionary: NSDictionary = ["key1": ["key2": ["key3": value]]] 78 | // // when 79 | // let result: Int = try! dictionary => "key1" => "key2" => "key3" 80 | // // then 81 | // XCTAssertEqual(result, value) 82 | // } 83 | 84 | func testDecodeNestedDictionaryCastingSuccess() { 85 | // given 86 | 87 | let value: NSDictionary = ["aKey" : "value"] 88 | let dictionary: NSDictionary = ["key": ["key": value]] 89 | // when 90 | let result = try! dictionary => "key" => "key" as! [String: String] 91 | // then 92 | XCTAssertEqual(result, value as! [String : String]) 93 | } 94 | 95 | func testDecodeAnyDecodableOptionalSuccess() { 96 | // given 97 | let key = "key" 98 | let value = "value" 99 | let dictionary: NSDictionary = [key: value] 100 | // when 101 | let string = try! dictionary => KeyPath(key) as String? 102 | // then 103 | XCTAssertEqual(string!, value) 104 | } 105 | 106 | func testDecodeAnyDecodableOptionalNilSuccess() { 107 | // given 108 | let key = "key" 109 | let dictionary: NSDictionary = [key: NSNull()] 110 | // when 111 | let string = try! dictionary => KeyPath(key) as String? 112 | // then 113 | XCTAssertNil(string) 114 | } 115 | 116 | func testDecodeAnyDecodableOptionalTypeMismatchFailure() { 117 | // given 118 | let key = "key" 119 | let dictionary: NSDictionary = [key: 2] 120 | // when 121 | do { 122 | let a = try dictionary => KeyPath(key) as String? 123 | print(a as Any) 124 | XCTFail() 125 | } catch let DecodingError.typeMismatch(_, actual, _) { 126 | let typeString = String(describing: actual) 127 | XCTAssertTrue(typeString.contains("Number"), "\(typeString) should contain NSNumber") 128 | } catch let error { 129 | XCTFail("should not throw \(error)") 130 | } 131 | } 132 | 133 | // MARK: - Nested =>? operators 134 | 135 | // Should throw on typemismatch with correct metadata 136 | func testDecodeNestedTypeMismatchFailure() { 137 | let json: NSDictionary = ["user": ["followers": "not_an_integer"]] 138 | do { 139 | let _ : Int? = try json =>? "user" => "followers" 140 | XCTFail("should throw") 141 | } catch let DecodingError.typeMismatch(_, _, metadata) { 142 | XCTAssertEqual(metadata.formattedPath, "user.followers") 143 | } catch { 144 | XCTFail("should not throw \(error)") 145 | } 146 | } 147 | 148 | // Should currently (though really it shoult not) treat all keys as either optional or non-optional 149 | func testDecodeNestedTypeReturnNilForSubobjectMissingKey() { 150 | let json: NSDictionary = ["user": ["something_else": "test"]] 151 | try! XCTAssertEqual(json =>? "user" =>? "followers", Optional.none) 152 | } 153 | 154 | // Sanity check 155 | func testDecodeNestedTypeSuccess() { 156 | let json: NSDictionary = ["user": ["followers": 3]] 157 | try! XCTAssertEqual(json =>? "user" => "followers", 3) 158 | } 159 | 160 | 161 | // MARK: => Errors 162 | 163 | /* // 164 | func testDecodeNestedDictionaryCastingFailure() { 165 | // given 166 | let value: NSDictionary = ["apple" : 2] 167 | let dictionary: NSDictionary = ["firstKey": ["secondKey": value]] 168 | // when 169 | do { 170 | _ = try dictionary => "firstKey" => "secondKey" as [String: String] 171 | XCTFail() 172 | } catch DecodingError.typeMismatchError(_, Dictionary.self, let info) { 173 | // then 174 | XCTAssertEqual(info.formattedPath, "firstKey.secondKey") 175 | XCTAssertEqual(info.object as? NSDictionary, value) 176 | } catch let error { 177 | XCTFail("should not throw \(error)") 178 | } 179 | } 180 | */ 181 | 182 | func testDecodeAnyDecodableThrowMissingKeyException() { 183 | // given 184 | let key = "key" 185 | let value = "value" 186 | let dictionary: NSDictionary = [key: value] 187 | // when 188 | do { 189 | _ = try dictionary => "nokey" as String 190 | } catch let DecodingError.missingKey(key, metadata) { 191 | // then 192 | XCTAssertEqual(key, "nokey") 193 | XCTAssertEqual(metadata.path, []) 194 | XCTAssertEqual(metadata.formattedPath, "") 195 | XCTAssertEqual(metadata.object as? NSDictionary, dictionary) 196 | } catch let error { 197 | XCTFail("should not throw \(error)") 198 | } 199 | } 200 | 201 | func testDecodeAnyDecodableThrowNoJsonObjectException() { 202 | // given 203 | let key = "key" 204 | let noDictionary: NSString = "hello" 205 | // when 206 | do { 207 | _ = try noDictionary => KeyPath(key) as String 208 | } catch let DecodingError.typeMismatch(expected, actual, metadata) where expected == NSDictionary.self { 209 | // then 210 | XCTAssertTrue(true) 211 | XCTAssertTrue(String(describing: actual).contains("String")) 212 | XCTAssertEqual(metadata.formattedPath, "") 213 | XCTAssertEqual(metadata.object as? NSString, (noDictionary)) 214 | } catch let error { 215 | XCTFail("should not throw \(error)") 216 | } 217 | } 218 | 219 | func testDecodeAnyDecodableDictionaryThrowMissingKeyException() { 220 | // given 221 | let key = "key" 222 | let value: NSDictionary = [key : "value"] 223 | let dictionary: NSDictionary = [key: value] 224 | // when 225 | do { 226 | _ = try dictionary => "nokey" 227 | } catch let DecodingError.missingKey(key, metadata) { 228 | // then 229 | XCTAssertEqual(key, "nokey") 230 | XCTAssertEqual(metadata.formattedPath, "") 231 | XCTAssertEqual(metadata.path, []) 232 | XCTAssertEqual(metadata.object as? NSDictionary, dictionary) 233 | } catch let error { 234 | XCTFail("should not throw \(error)") 235 | } 236 | } 237 | 238 | func testDecodeAnyDecodableDictionaryThrowJSONNotObjectException() { 239 | // given 240 | let key = "key" 241 | let noDictionary: NSString = "noDictionary" 242 | // when 243 | do { 244 | _ = try noDictionary => KeyPath(key) 245 | } catch let DecodingError.typeMismatch(expected, actual, metadata) where expected == NSDictionary.self { 246 | // then 247 | XCTAssertTrue(true) 248 | XCTAssertEqual(String(describing: actual), "__NSCFString") 249 | XCTAssertEqual(metadata.formattedPath, "") 250 | XCTAssertEqual(metadata.object as? NSString, noDictionary) 251 | } catch let error { 252 | XCTFail("should not throw \(error)") 253 | } 254 | } 255 | 256 | func testDecodeAnyDecodableDictionaryThrowTypeMismatchException() { 257 | // given 258 | let key = "key" 259 | let dictionary: NSDictionary = [key: "value"] 260 | // when 261 | do { 262 | _ = try dictionary => KeyPath(key) 263 | } catch DecodingError.typeMismatch { 264 | // then 265 | XCTAssertTrue(true) 266 | } catch let error { 267 | XCTFail("should not throw \(error)") 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /Decodable.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 17FB81011B530FED0012F106 /* Decodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17FB80F71B530FED0012F106 /* Decodable.framework */; }; 11 | 17FB810E1B5311840012F106 /* Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE7B57C1B4CA01400837609 /* Decodable.swift */; }; 12 | 17FB810F1B5311870012F106 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F956D1E1B4D6FF700243072 /* Operators.swift */; }; 13 | 57FCDE5B1BA283C900130C48 /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87BCC31B592F0E00E53A8C /* DecodingError.swift */; }; 14 | 57FCDE5C1BA283C900130C48 /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFAB8111B7CFA9500E2D724 /* Parse.swift */; }; 15 | 57FCDE5D1BA283C900130C48 /* Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE7B57C1B4CA01400837609 /* Decodable.swift */; }; 16 | 57FCDE5E1BA283C900130C48 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F956D1E1B4D6FF700243072 /* Operators.swift */; }; 17 | 651A8C971C29AC5F00DE4D53 /* RawRepresentableDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651A8C961C29AC5E00DE4D53 /* RawRepresentableDecodableTests.swift */; }; 18 | 651A8C981C29AC5F00DE4D53 /* RawRepresentableDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651A8C961C29AC5E00DE4D53 /* RawRepresentableDecodableTests.swift */; }; 19 | 65DB18B01C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65DB18AF1C29AC0E003BDA5C /* RawRepresentableDecodable.swift */; }; 20 | 65DB18B11C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65DB18AF1C29AC0E003BDA5C /* RawRepresentableDecodable.swift */; }; 21 | 65DB18B21C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65DB18AF1C29AC0E003BDA5C /* RawRepresentableDecodable.swift */; }; 22 | 65DB18B31C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65DB18AF1C29AC0E003BDA5C /* RawRepresentableDecodable.swift */; }; 23 | 8F00623F1C81EF61007BCF48 /* Overloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F00623E1C81EF61007BCF48 /* Overloads.swift */; }; 24 | 8F0062401C81EF61007BCF48 /* Overloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F00623E1C81EF61007BCF48 /* Overloads.swift */; }; 25 | 8F0062411C81EF61007BCF48 /* Overloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F00623E1C81EF61007BCF48 /* Overloads.swift */; }; 26 | 8F0062421C81EF61007BCF48 /* Overloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F00623E1C81EF61007BCF48 /* Overloads.swift */; }; 27 | 8F012EF51BB5A920007D0B5C /* Castable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F012EF41BB5A920007D0B5C /* Castable.swift */; }; 28 | 8F012EF61BB5A920007D0B5C /* Castable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F012EF41BB5A920007D0B5C /* Castable.swift */; }; 29 | 8F012EF71BB5A920007D0B5C /* Castable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F012EF41BB5A920007D0B5C /* Castable.swift */; }; 30 | 8F012EF81BB5A928007D0B5C /* Castable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F012EF41BB5A920007D0B5C /* Castable.swift */; }; 31 | 8F3E459A1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45991D31362B00FB71FC /* OptionalKeyPath.swift */; }; 32 | 8F3E459B1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45991D31362B00FB71FC /* OptionalKeyPath.swift */; }; 33 | 8F3E459C1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45991D31362B00FB71FC /* OptionalKeyPath.swift */; }; 34 | 8F3E459D1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45991D31362B00FB71FC /* OptionalKeyPath.swift */; }; 35 | 8F3E459F1D313A7000FB71FC /* KeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E459E1D313A7000FB71FC /* KeyPathTests.swift */; }; 36 | 8F3E45A01D313A7000FB71FC /* KeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E459E1D313A7000FB71FC /* KeyPathTests.swift */; }; 37 | 8F3E45A41D327E4500FB71FC /* Decoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45A31D327E4500FB71FC /* Decoders.swift */; }; 38 | 8F3E45A51D327E4500FB71FC /* Decoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45A31D327E4500FB71FC /* Decoders.swift */; }; 39 | 8F3E45A61D327E4500FB71FC /* Decoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45A31D327E4500FB71FC /* Decoders.swift */; }; 40 | 8F3E45A71D327E4500FB71FC /* Decoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45A31D327E4500FB71FC /* Decoders.swift */; }; 41 | 8F3E45B81D32884700FB71FC /* Documentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45AE1D32853F00FB71FC /* Documentation.swift */; }; 42 | 8F4453EF1D369FF200C19099 /* ParseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F4453EE1D369FF200C19099 /* ParseTests.swift */; }; 43 | 8F4453F01D369FF200C19099 /* ParseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F4453EE1D369FF200C19099 /* ParseTests.swift */; }; 44 | 8F4B52651B5BAA5700FDCBA7 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F4B52641B5BAA5700FDCBA7 /* ArrayTests.swift */; }; 45 | 8F4B52661B5BAA5700FDCBA7 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F4B52641B5BAA5700FDCBA7 /* ArrayTests.swift */; }; 46 | 8F53521D1BE4112900E3563A /* DictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F53521C1BE4112900E3563A /* DictionaryTests.swift */; }; 47 | 8F53521E1BE4112900E3563A /* DictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F53521C1BE4112900E3563A /* DictionaryTests.swift */; }; 48 | 8F6FCF5F1D4B39FC00838CE4 /* DynamicDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6FCF5E1D4B39FC00838CE4 /* DynamicDecodableTests.swift */; }; 49 | 8F6FCF601D4B39FC00838CE4 /* DynamicDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6FCF5E1D4B39FC00838CE4 /* DynamicDecodableTests.swift */; }; 50 | 8F72DC561C3CB8C500A39E10 /* NSValueCastable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F72DC551C3CB8C500A39E10 /* NSValueCastable.swift */; }; 51 | 8F72DC571C3CB8C800A39E10 /* NSValueCastable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F72DC551C3CB8C500A39E10 /* NSValueCastable.swift */; }; 52 | 8F72DC581C3CB8C900A39E10 /* NSValueCastable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F72DC551C3CB8C500A39E10 /* NSValueCastable.swift */; }; 53 | 8F72DC591C3CB8C900A39E10 /* NSValueCastable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F72DC551C3CB8C500A39E10 /* NSValueCastable.swift */; }; 54 | 8F84E70F1C3CB92D001EA4CE /* NSValueDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F84E70E1C3CB92D001EA4CE /* NSValueDecodableTests.swift */; }; 55 | 8F84E7101C3CB92D001EA4CE /* NSValueDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F84E70E1C3CB92D001EA4CE /* NSValueDecodableTests.swift */; }; 56 | 8F85E7781E13DA16000D6989 /* NSNullTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F85E7771E13DA16000D6989 /* NSNullTests.swift */; }; 57 | 8F85E7791E13DA16000D6989 /* NSNullTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F85E7771E13DA16000D6989 /* NSNullTests.swift */; }; 58 | 8F87BCBB1B580CE200E53A8C /* ErrorPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87BCBA1B580CE200E53A8C /* ErrorPathTests.swift */; }; 59 | 8F87BCBC1B580CE200E53A8C /* ErrorPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87BCBA1B580CE200E53A8C /* ErrorPathTests.swift */; }; 60 | 8F87BCC41B592F0E00E53A8C /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87BCC31B592F0E00E53A8C /* DecodingError.swift */; }; 61 | 8F87BCC51B592F0E00E53A8C /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87BCC31B592F0E00E53A8C /* DecodingError.swift */; }; 62 | 8F956D1F1B4D6FF700243072 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F956D1E1B4D6FF700243072 /* Operators.swift */; }; 63 | 8FA733591D328D13003A90A7 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45AF1D32853F00FB71FC /* Header.swift */; }; 64 | 8FB48ECA1D306C4700BC50A1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */; }; 65 | 8FB48ECB1D306C4700BC50A1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */; }; 66 | 8FB48ECC1D306C4700BC50A1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */; }; 67 | 8FB48ECD1D306C4700BC50A1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */; }; 68 | 8FD3D92F1C270A2D00D1AF4E /* MissingKeyOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD3D92E1C270A2D00D1AF4E /* MissingKeyOperatorTests.swift */; }; 69 | 8FD3D9301C270A2D00D1AF4E /* MissingKeyOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD3D92E1C270A2D00D1AF4E /* MissingKeyOperatorTests.swift */; }; 70 | 8FE7B5661B4C9FB900837609 /* Decodable.h in Headers */ = {isa = PBXBuildFile; fileRef = 8FE7B5651B4C9FB900837609 /* Decodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 71 | 8FE7B56D1B4C9FB900837609 /* Decodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8FE7B5621B4C9FB900837609 /* Decodable.framework */; }; 72 | 8FE7B5721B4C9FB900837609 /* DecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE7B5711B4C9FB900837609 /* DecodableTests.swift */; }; 73 | 8FE7B57E1B4CA01400837609 /* Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE7B57C1B4CA01400837609 /* Decodable.swift */; }; 74 | 8FFAB8121B7CFA9500E2D724 /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFAB8111B7CFA9500E2D724 /* Parse.swift */; }; 75 | 8FFAB8131B7CFA9500E2D724 /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFAB8111B7CFA9500E2D724 /* Parse.swift */; }; 76 | 8FFAB8141B7CFA9500E2D724 /* Parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFAB8111B7CFA9500E2D724 /* Parse.swift */; }; 77 | 9E2DA75C1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DA75B1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift */; }; 78 | 9E2DA75D1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DA75B1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift */; }; 79 | 9E2DA75F1CBC784700CAF3DF /* Vehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DA75E1CBC784700CAF3DF /* Vehicle.swift */; }; 80 | 9E2DA7601CBC784700CAF3DF /* Vehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DA75E1CBC784700CAF3DF /* Vehicle.swift */; }; 81 | 9E2DA7651CBC802200CAF3DF /* Vehicle.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E2DA7641CBC802200CAF3DF /* Vehicle.json */; }; 82 | 9E2DA7661CBC802200CAF3DF /* Vehicle.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E2DA7641CBC802200CAF3DF /* Vehicle.json */; }; 83 | D0DC54771B78150900F79CB0 /* Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE7B57C1B4CA01400837609 /* Decodable.swift */; }; 84 | D0DC54781B78150900F79CB0 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F956D1E1B4D6FF700243072 /* Operators.swift */; }; 85 | D0DC547A1B78150900F79CB0 /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87BCC31B592F0E00E53A8C /* DecodingError.swift */; }; 86 | FF0060981B5453C600D8CB77 /* DecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE7B5711B4C9FB900837609 /* DecodableTests.swift */; }; 87 | FF00609E1B5454F400D8CB77 /* DecodableExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF00609D1B5454F400D8CB77 /* DecodableExtensionTests.swift */; }; 88 | FF00609F1B5454F400D8CB77 /* DecodableExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF00609D1B5454F400D8CB77 /* DecodableExtensionTests.swift */; }; 89 | FF0060A41B5464FD00D8CB77 /* MissingKey.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A01B5464FD00D8CB77 /* MissingKey.json */; }; 90 | FF0060A51B5464FD00D8CB77 /* MissingKey.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A01B5464FD00D8CB77 /* MissingKey.json */; }; 91 | FF0060A61B5464FD00D8CB77 /* NoJsonObject.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A11B5464FD00D8CB77 /* NoJsonObject.json */; }; 92 | FF0060A71B5464FD00D8CB77 /* NoJsonObject.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A11B5464FD00D8CB77 /* NoJsonObject.json */; }; 93 | FF0060A81B5464FD00D8CB77 /* Repository.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A21B5464FD00D8CB77 /* Repository.json */; }; 94 | FF0060A91B5464FD00D8CB77 /* Repository.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A21B5464FD00D8CB77 /* Repository.json */; }; 95 | FF0060AA1B5464FD00D8CB77 /* TypeMismatch.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A31B5464FD00D8CB77 /* TypeMismatch.json */; }; 96 | FF0060AB1B5464FD00D8CB77 /* TypeMismatch.json in Resources */ = {isa = PBXBuildFile; fileRef = FF0060A31B5464FD00D8CB77 /* TypeMismatch.json */; }; 97 | FF0060B01B546FB100D8CB77 /* DecodableOperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0060AF1B546FB100D8CB77 /* DecodableOperatorsTests.swift */; }; 98 | FF0060B11B546FB100D8CB77 /* DecodableOperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0060AF1B546FB100D8CB77 /* DecodableOperatorsTests.swift */; }; 99 | FFE77E211B5396FB00E52F28 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE77E1E1B5391AD00E52F28 /* Repository.swift */; }; 100 | FFE77E221B5396FC00E52F28 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE77E1E1B5391AD00E52F28 /* Repository.swift */; }; 101 | /* End PBXBuildFile section */ 102 | 103 | /* Begin PBXContainerItemProxy section */ 104 | 17FB81021B530FED0012F106 /* PBXContainerItemProxy */ = { 105 | isa = PBXContainerItemProxy; 106 | containerPortal = 8FE7B5591B4C9FB900837609 /* Project object */; 107 | proxyType = 1; 108 | remoteGlobalIDString = 17FB80F61B530FED0012F106; 109 | remoteInfo = "Decodable-Mac"; 110 | }; 111 | 8FE7B56E1B4C9FB900837609 /* PBXContainerItemProxy */ = { 112 | isa = PBXContainerItemProxy; 113 | containerPortal = 8FE7B5591B4C9FB900837609 /* Project object */; 114 | proxyType = 1; 115 | remoteGlobalIDString = 8FE7B5611B4C9FB900837609; 116 | remoteInfo = Decodable; 117 | }; 118 | /* End PBXContainerItemProxy section */ 119 | 120 | /* Begin PBXFileReference section */ 121 | 17FB80F71B530FED0012F106 /* Decodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Decodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 122 | 17FB81001B530FED0012F106 /* DecodableTests-Mac.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DecodableTests-Mac.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 123 | 57FCDE651BA283C900130C48 /* Decodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Decodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 124 | 651A8C961C29AC5E00DE4D53 /* RawRepresentableDecodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawRepresentableDecodableTests.swift; sourceTree = ""; }; 125 | 65DB18AF1C29AC0E003BDA5C /* RawRepresentableDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawRepresentableDecodable.swift; sourceTree = ""; }; 126 | 8F00623E1C81EF61007BCF48 /* Overloads.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Overloads.swift; sourceTree = ""; }; 127 | 8F0062481C81F4A2007BCF48 /* Generator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generator.swift; sourceTree = ""; }; 128 | 8F012EEF1BB414D4007D0B5C /* Playground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground.playground; sourceTree = ""; }; 129 | 8F012EF41BB5A920007D0B5C /* Castable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Castable.swift; sourceTree = ""; }; 130 | 8F3E45991D31362B00FB71FC /* OptionalKeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalKeyPath.swift; sourceTree = ""; }; 131 | 8F3E459E1D313A7000FB71FC /* KeyPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPathTests.swift; sourceTree = ""; }; 132 | 8F3E45A31D327E4500FB71FC /* Decoders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decoders.swift; sourceTree = ""; }; 133 | 8F3E45AE1D32853F00FB71FC /* Documentation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Documentation.swift; sourceTree = ""; }; 134 | 8F3E45AF1D32853F00FB71FC /* Header.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = ""; }; 135 | 8F4453EE1D369FF200C19099 /* ParseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParseTests.swift; sourceTree = ""; }; 136 | 8F4B52641B5BAA5700FDCBA7 /* ArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = ""; }; 137 | 8F53521C1BE4112900E3563A /* DictionaryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryTests.swift; sourceTree = ""; }; 138 | 8F6FCF5E1D4B39FC00838CE4 /* DynamicDecodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicDecodableTests.swift; sourceTree = ""; }; 139 | 8F72DC551C3CB8C500A39E10 /* NSValueCastable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSValueCastable.swift; sourceTree = ""; }; 140 | 8F84E70E1C3CB92D001EA4CE /* NSValueDecodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSValueDecodableTests.swift; sourceTree = ""; }; 141 | 8F85E7771E13DA16000D6989 /* NSNullTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSNullTests.swift; sourceTree = ""; }; 142 | 8F87BCBA1B580CE200E53A8C /* ErrorPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorPathTests.swift; sourceTree = ""; }; 143 | 8F87BCC31B592F0E00E53A8C /* DecodingError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodingError.swift; sourceTree = ""; }; 144 | 8F956D1E1B4D6FF700243072 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; 145 | 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPath.swift; sourceTree = ""; }; 146 | 8FD3D92E1C270A2D00D1AF4E /* MissingKeyOperatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MissingKeyOperatorTests.swift; sourceTree = ""; }; 147 | 8FE7B5621B4C9FB900837609 /* Decodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Decodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 148 | 8FE7B5651B4C9FB900837609 /* Decodable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Decodable.h; sourceTree = ""; }; 149 | 8FE7B5671B4C9FB900837609 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 150 | 8FE7B56C1B4C9FB900837609 /* DecodableTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DecodableTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 151 | 8FE7B5711B4C9FB900837609 /* DecodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodableTests.swift; sourceTree = ""; }; 152 | 8FE7B5731B4C9FB900837609 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 153 | 8FE7B57C1B4CA01400837609 /* Decodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decodable.swift; sourceTree = ""; }; 154 | 8FFAB8111B7CFA9500E2D724 /* Parse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parse.swift; sourceTree = ""; }; 155 | 9E2DA75B1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodeAsOneOfTests.swift; sourceTree = ""; }; 156 | 9E2DA75E1CBC784700CAF3DF /* Vehicle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; 157 | 9E2DA7641CBC802200CAF3DF /* Vehicle.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Vehicle.json; path = JSONExamples/Vehicle.json; sourceTree = ""; }; 158 | D0DC546F1B7814D200F79CB0 /* Decodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Decodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 159 | FF00609D1B5454F400D8CB77 /* DecodableExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodableExtensionTests.swift; sourceTree = ""; }; 160 | FF0060A01B5464FD00D8CB77 /* MissingKey.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = MissingKey.json; path = JSONExamples/MissingKey.json; sourceTree = ""; }; 161 | FF0060A11B5464FD00D8CB77 /* NoJsonObject.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = NoJsonObject.json; path = JSONExamples/NoJsonObject.json; sourceTree = ""; }; 162 | FF0060A21B5464FD00D8CB77 /* Repository.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Repository.json; path = JSONExamples/Repository.json; sourceTree = ""; }; 163 | FF0060A31B5464FD00D8CB77 /* TypeMismatch.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = TypeMismatch.json; path = JSONExamples/TypeMismatch.json; sourceTree = ""; }; 164 | FF0060AF1B546FB100D8CB77 /* DecodableOperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodableOperatorsTests.swift; sourceTree = ""; }; 165 | FFE77E1E1B5391AD00E52F28 /* Repository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; 166 | /* End PBXFileReference section */ 167 | 168 | /* Begin PBXFrameworksBuildPhase section */ 169 | 17FB80F31B530FED0012F106 /* Frameworks */ = { 170 | isa = PBXFrameworksBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | 17FB80FD1B530FED0012F106 /* Frameworks */ = { 177 | isa = PBXFrameworksBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | 17FB81011B530FED0012F106 /* Decodable.framework in Frameworks */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | 57FCDE5F1BA283C900130C48 /* Frameworks */ = { 185 | isa = PBXFrameworksBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | 8FE7B55E1B4C9FB900837609 /* Frameworks */ = { 192 | isa = PBXFrameworksBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | 8FE7B5691B4C9FB900837609 /* Frameworks */ = { 199 | isa = PBXFrameworksBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | 8FE7B56D1B4C9FB900837609 /* Decodable.framework in Frameworks */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | D0DC546B1B7814D200F79CB0 /* Frameworks */ = { 207 | isa = PBXFrameworksBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXFrameworksBuildPhase section */ 214 | 215 | /* Begin PBXGroup section */ 216 | 8F0062471C81F4A2007BCF48 /* Code Generation */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | 8F0062481C81F4A2007BCF48 /* Generator.swift */, 220 | 8F3E45AD1D32853F00FB71FC /* Templates */, 221 | ); 222 | name = "Code Generation"; 223 | path = Generator; 224 | sourceTree = ""; 225 | }; 226 | 8F3E45AD1D32853F00FB71FC /* Templates */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 8F3E45AE1D32853F00FB71FC /* Documentation.swift */, 230 | 8F3E45AF1D32853F00FB71FC /* Header.swift */, 231 | ); 232 | path = Templates; 233 | sourceTree = ""; 234 | }; 235 | 8FE7B5581B4C9FB900837609 = { 236 | isa = PBXGroup; 237 | children = ( 238 | 8FE7B5641B4C9FB900837609 /* Decodable */, 239 | 8F0062471C81F4A2007BCF48 /* Code Generation */, 240 | 8FE7B5701B4C9FB900837609 /* DecodableTests */, 241 | 8FE7B5631B4C9FB900837609 /* Products */, 242 | ); 243 | sourceTree = ""; 244 | }; 245 | 8FE7B5631B4C9FB900837609 /* Products */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 8FE7B5621B4C9FB900837609 /* Decodable.framework */, 249 | 8FE7B56C1B4C9FB900837609 /* DecodableTests-iOS.xctest */, 250 | 17FB80F71B530FED0012F106 /* Decodable.framework */, 251 | 17FB81001B530FED0012F106 /* DecodableTests-Mac.xctest */, 252 | D0DC546F1B7814D200F79CB0 /* Decodable.framework */, 253 | 57FCDE651BA283C900130C48 /* Decodable.framework */, 254 | ); 255 | name = Products; 256 | sourceTree = ""; 257 | }; 258 | 8FE7B5641B4C9FB900837609 /* Decodable */ = { 259 | isa = PBXGroup; 260 | children = ( 261 | 8FE7B5651B4C9FB900837609 /* Decodable.h */, 262 | 8F012EEF1BB414D4007D0B5C /* Playground.playground */, 263 | 65DB18AF1C29AC0E003BDA5C /* RawRepresentableDecodable.swift */, 264 | 8F72DC551C3CB8C500A39E10 /* NSValueCastable.swift */, 265 | 8F3E45A31D327E4500FB71FC /* Decoders.swift */, 266 | 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */, 267 | 8F3E45991D31362B00FB71FC /* OptionalKeyPath.swift */, 268 | 8F956D1E1B4D6FF700243072 /* Operators.swift */, 269 | 8F00623E1C81EF61007BCF48 /* Overloads.swift */, 270 | 8FE7B57C1B4CA01400837609 /* Decodable.swift */, 271 | 8F012EF41BB5A920007D0B5C /* Castable.swift */, 272 | 8FFAB8111B7CFA9500E2D724 /* Parse.swift */, 273 | 8F87BCC31B592F0E00E53A8C /* DecodingError.swift */, 274 | 8FE7B5671B4C9FB900837609 /* Info.plist */, 275 | ); 276 | name = Decodable; 277 | path = Sources; 278 | sourceTree = ""; 279 | }; 280 | 8FE7B5701B4C9FB900837609 /* DecodableTests */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | FF0060991B54544A00D8CB77 /* JSONExamples */, 284 | 8FE7B5711B4C9FB900837609 /* DecodableTests.swift */, 285 | 8F4453EE1D369FF200C19099 /* ParseTests.swift */, 286 | 8F6FCF5E1D4B39FC00838CE4 /* DynamicDecodableTests.swift */, 287 | 8F3E459E1D313A7000FB71FC /* KeyPathTests.swift */, 288 | FF00609D1B5454F400D8CB77 /* DecodableExtensionTests.swift */, 289 | 9E2DA75B1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift */, 290 | 8F84E70E1C3CB92D001EA4CE /* NSValueDecodableTests.swift */, 291 | FF0060AF1B546FB100D8CB77 /* DecodableOperatorsTests.swift */, 292 | 8F85E7771E13DA16000D6989 /* NSNullTests.swift */, 293 | 8FD3D92E1C270A2D00D1AF4E /* MissingKeyOperatorTests.swift */, 294 | 651A8C961C29AC5E00DE4D53 /* RawRepresentableDecodableTests.swift */, 295 | 8F4B52641B5BAA5700FDCBA7 /* ArrayTests.swift */, 296 | 8F53521C1BE4112900E3563A /* DictionaryTests.swift */, 297 | 8F87BCBA1B580CE200E53A8C /* ErrorPathTests.swift */, 298 | FFE77E1E1B5391AD00E52F28 /* Repository.swift */, 299 | 9E2DA75E1CBC784700CAF3DF /* Vehicle.swift */, 300 | 8FE7B5731B4C9FB900837609 /* Info.plist */, 301 | ); 302 | name = DecodableTests; 303 | path = Tests; 304 | sourceTree = ""; 305 | }; 306 | FF0060991B54544A00D8CB77 /* JSONExamples */ = { 307 | isa = PBXGroup; 308 | children = ( 309 | FF0060A01B5464FD00D8CB77 /* MissingKey.json */, 310 | FF0060A11B5464FD00D8CB77 /* NoJsonObject.json */, 311 | FF0060A21B5464FD00D8CB77 /* Repository.json */, 312 | FF0060A31B5464FD00D8CB77 /* TypeMismatch.json */, 313 | 9E2DA7641CBC802200CAF3DF /* Vehicle.json */, 314 | ); 315 | name = JSONExamples; 316 | sourceTree = ""; 317 | }; 318 | /* End PBXGroup section */ 319 | 320 | /* Begin PBXHeadersBuildPhase section */ 321 | 17FB80F41B530FED0012F106 /* Headers */ = { 322 | isa = PBXHeadersBuildPhase; 323 | buildActionMask = 2147483647; 324 | files = ( 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | 57FCDE601BA283C900130C48 /* Headers */ = { 329 | isa = PBXHeadersBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | 8FE7B55F1B4C9FB900837609 /* Headers */ = { 336 | isa = PBXHeadersBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | 8FE7B5661B4C9FB900837609 /* Decodable.h in Headers */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | D0DC546C1B7814D200F79CB0 /* Headers */ = { 344 | isa = PBXHeadersBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | }; 350 | /* End PBXHeadersBuildPhase section */ 351 | 352 | /* Begin PBXNativeTarget section */ 353 | 17FB80F61B530FED0012F106 /* Decodable-Mac */ = { 354 | isa = PBXNativeTarget; 355 | buildConfigurationList = 17FB81081B530FED0012F106 /* Build configuration list for PBXNativeTarget "Decodable-Mac" */; 356 | buildPhases = ( 357 | 8F0062451C81F348007BCF48 /* ShellScript */, 358 | 17FB80F21B530FED0012F106 /* Sources */, 359 | 17FB80F31B530FED0012F106 /* Frameworks */, 360 | 17FB80F41B530FED0012F106 /* Headers */, 361 | 17FB80F51B530FED0012F106 /* Resources */, 362 | ); 363 | buildRules = ( 364 | ); 365 | dependencies = ( 366 | ); 367 | name = "Decodable-Mac"; 368 | productName = "Decodable-Mac"; 369 | productReference = 17FB80F71B530FED0012F106 /* Decodable.framework */; 370 | productType = "com.apple.product-type.framework"; 371 | }; 372 | 17FB80FF1B530FED0012F106 /* DecodableTests-Mac */ = { 373 | isa = PBXNativeTarget; 374 | buildConfigurationList = 17FB810B1B530FED0012F106 /* Build configuration list for PBXNativeTarget "DecodableTests-Mac" */; 375 | buildPhases = ( 376 | 17FB80FC1B530FED0012F106 /* Sources */, 377 | 17FB80FD1B530FED0012F106 /* Frameworks */, 378 | 17FB80FE1B530FED0012F106 /* Resources */, 379 | ); 380 | buildRules = ( 381 | ); 382 | dependencies = ( 383 | 17FB81031B530FED0012F106 /* PBXTargetDependency */, 384 | ); 385 | name = "DecodableTests-Mac"; 386 | productName = "Decodable-MacTests"; 387 | productReference = 17FB81001B530FED0012F106 /* DecodableTests-Mac.xctest */; 388 | productType = "com.apple.product-type.bundle.unit-test"; 389 | }; 390 | 57FCDE591BA283C900130C48 /* Decodable-tvOS */ = { 391 | isa = PBXNativeTarget; 392 | buildConfigurationList = 57FCDE621BA283C900130C48 /* Build configuration list for PBXNativeTarget "Decodable-tvOS" */; 393 | buildPhases = ( 394 | 8F00624B1C81F6F1007BCF48 /* ShellScript */, 395 | 57FCDE5A1BA283C900130C48 /* Sources */, 396 | 57FCDE5F1BA283C900130C48 /* Frameworks */, 397 | 57FCDE601BA283C900130C48 /* Headers */, 398 | 57FCDE611BA283C900130C48 /* Resources */, 399 | ); 400 | buildRules = ( 401 | ); 402 | dependencies = ( 403 | ); 404 | name = "Decodable-tvOS"; 405 | productName = "Decodable-watchOS"; 406 | productReference = 57FCDE651BA283C900130C48 /* Decodable.framework */; 407 | productType = "com.apple.product-type.framework"; 408 | }; 409 | 8FE7B5611B4C9FB900837609 /* Decodable-iOS */ = { 410 | isa = PBXNativeTarget; 411 | buildConfigurationList = 8FE7B5761B4C9FB900837609 /* Build configuration list for PBXNativeTarget "Decodable-iOS" */; 412 | buildPhases = ( 413 | 8F0062441C81F26B007BCF48 /* ShellScript */, 414 | 8FE7B55D1B4C9FB900837609 /* Sources */, 415 | 8FE7B55E1B4C9FB900837609 /* Frameworks */, 416 | 8FE7B55F1B4C9FB900837609 /* Headers */, 417 | 8FE7B5601B4C9FB900837609 /* Resources */, 418 | ); 419 | buildRules = ( 420 | ); 421 | dependencies = ( 422 | ); 423 | name = "Decodable-iOS"; 424 | productName = Decodable; 425 | productReference = 8FE7B5621B4C9FB900837609 /* Decodable.framework */; 426 | productType = "com.apple.product-type.framework"; 427 | }; 428 | 8FE7B56B1B4C9FB900837609 /* DecodableTests-iOS */ = { 429 | isa = PBXNativeTarget; 430 | buildConfigurationList = 8FE7B5791B4C9FB900837609 /* Build configuration list for PBXNativeTarget "DecodableTests-iOS" */; 431 | buildPhases = ( 432 | 8FE7B5681B4C9FB900837609 /* Sources */, 433 | 8FE7B5691B4C9FB900837609 /* Frameworks */, 434 | 8FE7B56A1B4C9FB900837609 /* Resources */, 435 | ); 436 | buildRules = ( 437 | ); 438 | dependencies = ( 439 | 8FE7B56F1B4C9FB900837609 /* PBXTargetDependency */, 440 | ); 441 | name = "DecodableTests-iOS"; 442 | productName = DecodableTests; 443 | productReference = 8FE7B56C1B4C9FB900837609 /* DecodableTests-iOS.xctest */; 444 | productType = "com.apple.product-type.bundle.unit-test"; 445 | }; 446 | D0DC546E1B7814D200F79CB0 /* Decodable-watchOS */ = { 447 | isa = PBXNativeTarget; 448 | buildConfigurationList = D0DC54761B7814D200F79CB0 /* Build configuration list for PBXNativeTarget "Decodable-watchOS" */; 449 | buildPhases = ( 450 | 8F0062461C81F350007BCF48 /* ShellScript */, 451 | D0DC546A1B7814D200F79CB0 /* Sources */, 452 | D0DC546B1B7814D200F79CB0 /* Frameworks */, 453 | D0DC546C1B7814D200F79CB0 /* Headers */, 454 | D0DC546D1B7814D200F79CB0 /* Resources */, 455 | ); 456 | buildRules = ( 457 | ); 458 | dependencies = ( 459 | ); 460 | name = "Decodable-watchOS"; 461 | productName = "Decodable-watchOS"; 462 | productReference = D0DC546F1B7814D200F79CB0 /* Decodable.framework */; 463 | productType = "com.apple.product-type.framework"; 464 | }; 465 | /* End PBXNativeTarget section */ 466 | 467 | /* Begin PBXProject section */ 468 | 8FE7B5591B4C9FB900837609 /* Project object */ = { 469 | isa = PBXProject; 470 | attributes = { 471 | LastSwiftUpdateCheck = 0700; 472 | LastUpgradeCheck = 0900; 473 | ORGANIZATIONNAME = anviking; 474 | TargetAttributes = { 475 | 17FB80F61B530FED0012F106 = { 476 | CreatedOnToolsVersion = 7.0; 477 | }; 478 | 17FB80FF1B530FED0012F106 = { 479 | CreatedOnToolsVersion = 7.0; 480 | }; 481 | 8FE7B5611B4C9FB900837609 = { 482 | CreatedOnToolsVersion = 7.0; 483 | LastSwiftMigration = 0900; 484 | }; 485 | 8FE7B56B1B4C9FB900837609 = { 486 | CreatedOnToolsVersion = 7.0; 487 | LastSwiftMigration = 0900; 488 | }; 489 | D0DC546E1B7814D200F79CB0 = { 490 | CreatedOnToolsVersion = 7.0; 491 | LastSwiftMigration = 0800; 492 | }; 493 | }; 494 | }; 495 | buildConfigurationList = 8FE7B55C1B4C9FB900837609 /* Build configuration list for PBXProject "Decodable" */; 496 | compatibilityVersion = "Xcode 3.2"; 497 | developmentRegion = English; 498 | hasScannedForEncodings = 0; 499 | knownRegions = ( 500 | en, 501 | ); 502 | mainGroup = 8FE7B5581B4C9FB900837609; 503 | productRefGroup = 8FE7B5631B4C9FB900837609 /* Products */; 504 | projectDirPath = ""; 505 | projectRoot = ""; 506 | targets = ( 507 | 8FE7B5611B4C9FB900837609 /* Decodable-iOS */, 508 | 8FE7B56B1B4C9FB900837609 /* DecodableTests-iOS */, 509 | 17FB80F61B530FED0012F106 /* Decodable-Mac */, 510 | 17FB80FF1B530FED0012F106 /* DecodableTests-Mac */, 511 | D0DC546E1B7814D200F79CB0 /* Decodable-watchOS */, 512 | 57FCDE591BA283C900130C48 /* Decodable-tvOS */, 513 | ); 514 | }; 515 | /* End PBXProject section */ 516 | 517 | /* Begin PBXResourcesBuildPhase section */ 518 | 17FB80F51B530FED0012F106 /* Resources */ = { 519 | isa = PBXResourcesBuildPhase; 520 | buildActionMask = 2147483647; 521 | files = ( 522 | ); 523 | runOnlyForDeploymentPostprocessing = 0; 524 | }; 525 | 17FB80FE1B530FED0012F106 /* Resources */ = { 526 | isa = PBXResourcesBuildPhase; 527 | buildActionMask = 2147483647; 528 | files = ( 529 | FF0060A51B5464FD00D8CB77 /* MissingKey.json in Resources */, 530 | FF0060AB1B5464FD00D8CB77 /* TypeMismatch.json in Resources */, 531 | 9E2DA7661CBC802200CAF3DF /* Vehicle.json in Resources */, 532 | FF0060A91B5464FD00D8CB77 /* Repository.json in Resources */, 533 | FF0060A71B5464FD00D8CB77 /* NoJsonObject.json in Resources */, 534 | ); 535 | runOnlyForDeploymentPostprocessing = 0; 536 | }; 537 | 57FCDE611BA283C900130C48 /* Resources */ = { 538 | isa = PBXResourcesBuildPhase; 539 | buildActionMask = 2147483647; 540 | files = ( 541 | ); 542 | runOnlyForDeploymentPostprocessing = 0; 543 | }; 544 | 8FE7B5601B4C9FB900837609 /* Resources */ = { 545 | isa = PBXResourcesBuildPhase; 546 | buildActionMask = 2147483647; 547 | files = ( 548 | ); 549 | runOnlyForDeploymentPostprocessing = 0; 550 | }; 551 | 8FE7B56A1B4C9FB900837609 /* Resources */ = { 552 | isa = PBXResourcesBuildPhase; 553 | buildActionMask = 2147483647; 554 | files = ( 555 | FF0060A41B5464FD00D8CB77 /* MissingKey.json in Resources */, 556 | FF0060AA1B5464FD00D8CB77 /* TypeMismatch.json in Resources */, 557 | 9E2DA7651CBC802200CAF3DF /* Vehicle.json in Resources */, 558 | FF0060A81B5464FD00D8CB77 /* Repository.json in Resources */, 559 | FF0060A61B5464FD00D8CB77 /* NoJsonObject.json in Resources */, 560 | ); 561 | runOnlyForDeploymentPostprocessing = 0; 562 | }; 563 | D0DC546D1B7814D200F79CB0 /* Resources */ = { 564 | isa = PBXResourcesBuildPhase; 565 | buildActionMask = 2147483647; 566 | files = ( 567 | ); 568 | runOnlyForDeploymentPostprocessing = 0; 569 | }; 570 | /* End PBXResourcesBuildPhase section */ 571 | 572 | /* Begin PBXShellScriptBuildPhase section */ 573 | 8F0062441C81F26B007BCF48 /* ShellScript */ = { 574 | isa = PBXShellScriptBuildPhase; 575 | buildActionMask = 2147483647; 576 | files = ( 577 | ); 578 | inputPaths = ( 579 | ); 580 | outputPaths = ( 581 | ); 582 | runOnlyForDeploymentPostprocessing = 0; 583 | shellPath = /bin/sh; 584 | shellScript = "if [ -z \"$CI\" ]; then\n cd \"${SRCROOT}/Generator\"\n ./Generator.swift\nfi"; 585 | }; 586 | 8F0062451C81F348007BCF48 /* ShellScript */ = { 587 | isa = PBXShellScriptBuildPhase; 588 | buildActionMask = 2147483647; 589 | files = ( 590 | ); 591 | inputPaths = ( 592 | ); 593 | outputPaths = ( 594 | ); 595 | runOnlyForDeploymentPostprocessing = 0; 596 | shellPath = /bin/sh; 597 | shellScript = "if [ -z \"$CI\" ]; then\n cd \"${SRCROOT}/Generator\"\n ./Generator.swift\nfi"; 598 | }; 599 | 8F0062461C81F350007BCF48 /* ShellScript */ = { 600 | isa = PBXShellScriptBuildPhase; 601 | buildActionMask = 2147483647; 602 | files = ( 603 | ); 604 | inputPaths = ( 605 | ); 606 | outputPaths = ( 607 | ); 608 | runOnlyForDeploymentPostprocessing = 0; 609 | shellPath = /bin/sh; 610 | shellScript = "if [ -z \"$CI\" ]; then\n cd \"${SRCROOT}/Generator\"\n ./Generator.swift\nfi"; 611 | }; 612 | 8F00624B1C81F6F1007BCF48 /* ShellScript */ = { 613 | isa = PBXShellScriptBuildPhase; 614 | buildActionMask = 2147483647; 615 | files = ( 616 | ); 617 | inputPaths = ( 618 | ); 619 | outputPaths = ( 620 | ); 621 | runOnlyForDeploymentPostprocessing = 0; 622 | shellPath = /bin/sh; 623 | shellScript = "if [ -z \"$CI\" ]; then\n cd \"${SRCROOT}/Generator\"\n ./Generator.swift\nfi"; 624 | }; 625 | /* End PBXShellScriptBuildPhase section */ 626 | 627 | /* Begin PBXSourcesBuildPhase section */ 628 | 17FB80F21B530FED0012F106 /* Sources */ = { 629 | isa = PBXSourcesBuildPhase; 630 | buildActionMask = 2147483647; 631 | files = ( 632 | 8F87BCC51B592F0E00E53A8C /* DecodingError.swift in Sources */, 633 | 8FFAB8131B7CFA9500E2D724 /* Parse.swift in Sources */, 634 | 8F012EF61BB5A920007D0B5C /* Castable.swift in Sources */, 635 | 8FB48ECB1D306C4700BC50A1 /* KeyPath.swift in Sources */, 636 | 17FB810E1B5311840012F106 /* Decodable.swift in Sources */, 637 | 17FB810F1B5311870012F106 /* Operators.swift in Sources */, 638 | 8F3E459B1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */, 639 | 8F0062401C81EF61007BCF48 /* Overloads.swift in Sources */, 640 | 65DB18B11C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */, 641 | 8F72DC571C3CB8C800A39E10 /* NSValueCastable.swift in Sources */, 642 | 8F3E45A51D327E4500FB71FC /* Decoders.swift in Sources */, 643 | ); 644 | runOnlyForDeploymentPostprocessing = 0; 645 | }; 646 | 17FB80FC1B530FED0012F106 /* Sources */ = { 647 | isa = PBXSourcesBuildPhase; 648 | buildActionMask = 2147483647; 649 | files = ( 650 | 9E2DA7601CBC784700CAF3DF /* Vehicle.swift in Sources */, 651 | 8F87BCBC1B580CE200E53A8C /* ErrorPathTests.swift in Sources */, 652 | 8F6FCF601D4B39FC00838CE4 /* DynamicDecodableTests.swift in Sources */, 653 | 651A8C981C29AC5F00DE4D53 /* RawRepresentableDecodableTests.swift in Sources */, 654 | FF00609F1B5454F400D8CB77 /* DecodableExtensionTests.swift in Sources */, 655 | 8FD3D9301C270A2D00D1AF4E /* MissingKeyOperatorTests.swift in Sources */, 656 | 8F53521E1BE4112900E3563A /* DictionaryTests.swift in Sources */, 657 | FF0060B11B546FB100D8CB77 /* DecodableOperatorsTests.swift in Sources */, 658 | 8F4453F01D369FF200C19099 /* ParseTests.swift in Sources */, 659 | 8F84E7101C3CB92D001EA4CE /* NSValueDecodableTests.swift in Sources */, 660 | 8F4B52661B5BAA5700FDCBA7 /* ArrayTests.swift in Sources */, 661 | 8F85E7791E13DA16000D6989 /* NSNullTests.swift in Sources */, 662 | FFE77E211B5396FB00E52F28 /* Repository.swift in Sources */, 663 | FF0060981B5453C600D8CB77 /* DecodableTests.swift in Sources */, 664 | 9E2DA75D1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift in Sources */, 665 | 8F3E45A01D313A7000FB71FC /* KeyPathTests.swift in Sources */, 666 | ); 667 | runOnlyForDeploymentPostprocessing = 0; 668 | }; 669 | 57FCDE5A1BA283C900130C48 /* Sources */ = { 670 | isa = PBXSourcesBuildPhase; 671 | buildActionMask = 2147483647; 672 | files = ( 673 | 57FCDE5B1BA283C900130C48 /* DecodingError.swift in Sources */, 674 | 57FCDE5C1BA283C900130C48 /* Parse.swift in Sources */, 675 | 8F012EF81BB5A928007D0B5C /* Castable.swift in Sources */, 676 | 8FB48ECD1D306C4700BC50A1 /* KeyPath.swift in Sources */, 677 | 57FCDE5D1BA283C900130C48 /* Decodable.swift in Sources */, 678 | 57FCDE5E1BA283C900130C48 /* Operators.swift in Sources */, 679 | 8F3E459D1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */, 680 | 8F0062421C81EF61007BCF48 /* Overloads.swift in Sources */, 681 | 65DB18B31C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */, 682 | 8F72DC591C3CB8C900A39E10 /* NSValueCastable.swift in Sources */, 683 | 8F3E45A71D327E4500FB71FC /* Decoders.swift in Sources */, 684 | ); 685 | runOnlyForDeploymentPostprocessing = 0; 686 | }; 687 | 8FE7B55D1B4C9FB900837609 /* Sources */ = { 688 | isa = PBXSourcesBuildPhase; 689 | buildActionMask = 2147483647; 690 | files = ( 691 | 8F87BCC41B592F0E00E53A8C /* DecodingError.swift in Sources */, 692 | 8FA733591D328D13003A90A7 /* Header.swift in Sources */, 693 | 8FFAB8121B7CFA9500E2D724 /* Parse.swift in Sources */, 694 | 8F012EF51BB5A920007D0B5C /* Castable.swift in Sources */, 695 | 8FB48ECA1D306C4700BC50A1 /* KeyPath.swift in Sources */, 696 | 8FE7B57E1B4CA01400837609 /* Decodable.swift in Sources */, 697 | 8F72DC561C3CB8C500A39E10 /* NSValueCastable.swift in Sources */, 698 | 8F3E459A1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */, 699 | 8F00623F1C81EF61007BCF48 /* Overloads.swift in Sources */, 700 | 8F3E45B81D32884700FB71FC /* Documentation.swift in Sources */, 701 | 8F956D1F1B4D6FF700243072 /* Operators.swift in Sources */, 702 | 65DB18B01C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */, 703 | 8F3E45A41D327E4500FB71FC /* Decoders.swift in Sources */, 704 | ); 705 | runOnlyForDeploymentPostprocessing = 0; 706 | }; 707 | 8FE7B5681B4C9FB900837609 /* Sources */ = { 708 | isa = PBXSourcesBuildPhase; 709 | buildActionMask = 2147483647; 710 | files = ( 711 | 9E2DA75F1CBC784700CAF3DF /* Vehicle.swift in Sources */, 712 | 8F87BCBB1B580CE200E53A8C /* ErrorPathTests.swift in Sources */, 713 | 8F6FCF5F1D4B39FC00838CE4 /* DynamicDecodableTests.swift in Sources */, 714 | 651A8C971C29AC5F00DE4D53 /* RawRepresentableDecodableTests.swift in Sources */, 715 | FF00609E1B5454F400D8CB77 /* DecodableExtensionTests.swift in Sources */, 716 | 8FD3D92F1C270A2D00D1AF4E /* MissingKeyOperatorTests.swift in Sources */, 717 | 8F53521D1BE4112900E3563A /* DictionaryTests.swift in Sources */, 718 | FF0060B01B546FB100D8CB77 /* DecodableOperatorsTests.swift in Sources */, 719 | 8F4453EF1D369FF200C19099 /* ParseTests.swift in Sources */, 720 | 8F84E70F1C3CB92D001EA4CE /* NSValueDecodableTests.swift in Sources */, 721 | 8F4B52651B5BAA5700FDCBA7 /* ArrayTests.swift in Sources */, 722 | 8F85E7781E13DA16000D6989 /* NSNullTests.swift in Sources */, 723 | 8FE7B5721B4C9FB900837609 /* DecodableTests.swift in Sources */, 724 | FFE77E221B5396FC00E52F28 /* Repository.swift in Sources */, 725 | 9E2DA75C1CBC77AB00CAF3DF /* DecodeAsOneOfTests.swift in Sources */, 726 | 8F3E459F1D313A7000FB71FC /* KeyPathTests.swift in Sources */, 727 | ); 728 | runOnlyForDeploymentPostprocessing = 0; 729 | }; 730 | D0DC546A1B7814D200F79CB0 /* Sources */ = { 731 | isa = PBXSourcesBuildPhase; 732 | buildActionMask = 2147483647; 733 | files = ( 734 | D0DC547A1B78150900F79CB0 /* DecodingError.swift in Sources */, 735 | 8FFAB8141B7CFA9500E2D724 /* Parse.swift in Sources */, 736 | 8F012EF71BB5A920007D0B5C /* Castable.swift in Sources */, 737 | 8FB48ECC1D306C4700BC50A1 /* KeyPath.swift in Sources */, 738 | D0DC54771B78150900F79CB0 /* Decodable.swift in Sources */, 739 | D0DC54781B78150900F79CB0 /* Operators.swift in Sources */, 740 | 8F3E459C1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */, 741 | 8F0062411C81EF61007BCF48 /* Overloads.swift in Sources */, 742 | 65DB18B21C29AC0E003BDA5C /* RawRepresentableDecodable.swift in Sources */, 743 | 8F72DC581C3CB8C900A39E10 /* NSValueCastable.swift in Sources */, 744 | 8F3E45A61D327E4500FB71FC /* Decoders.swift in Sources */, 745 | ); 746 | runOnlyForDeploymentPostprocessing = 0; 747 | }; 748 | /* End PBXSourcesBuildPhase section */ 749 | 750 | /* Begin PBXTargetDependency section */ 751 | 17FB81031B530FED0012F106 /* PBXTargetDependency */ = { 752 | isa = PBXTargetDependency; 753 | target = 17FB80F61B530FED0012F106 /* Decodable-Mac */; 754 | targetProxy = 17FB81021B530FED0012F106 /* PBXContainerItemProxy */; 755 | }; 756 | 8FE7B56F1B4C9FB900837609 /* PBXTargetDependency */ = { 757 | isa = PBXTargetDependency; 758 | target = 8FE7B5611B4C9FB900837609 /* Decodable-iOS */; 759 | targetProxy = 8FE7B56E1B4C9FB900837609 /* PBXContainerItemProxy */; 760 | }; 761 | /* End PBXTargetDependency section */ 762 | 763 | /* Begin XCBuildConfiguration section */ 764 | 17FB81091B530FED0012F106 /* Debug */ = { 765 | isa = XCBuildConfiguration; 766 | buildSettings = { 767 | APPLICATION_EXTENSION_API_ONLY = YES; 768 | COMBINE_HIDPI_IMAGES = YES; 769 | DEFINES_MODULE = YES; 770 | DYLIB_COMPATIBILITY_VERSION = 1; 771 | DYLIB_CURRENT_VERSION = 1; 772 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 773 | ENABLE_BITCODE = NO; 774 | FRAMEWORK_VERSION = A; 775 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 776 | INFOPLIST_FILE = Sources/Info.plist; 777 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 778 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 779 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 780 | PRODUCT_NAME = Decodable; 781 | SDKROOT = macosx; 782 | SKIP_INSTALL = YES; 783 | }; 784 | name = Debug; 785 | }; 786 | 17FB810A1B530FED0012F106 /* Release */ = { 787 | isa = XCBuildConfiguration; 788 | buildSettings = { 789 | APPLICATION_EXTENSION_API_ONLY = YES; 790 | COMBINE_HIDPI_IMAGES = YES; 791 | DEFINES_MODULE = YES; 792 | DYLIB_COMPATIBILITY_VERSION = 1; 793 | DYLIB_CURRENT_VERSION = 1; 794 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 795 | ENABLE_BITCODE = NO; 796 | FRAMEWORK_VERSION = A; 797 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 798 | INFOPLIST_FILE = Sources/Info.plist; 799 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 800 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 801 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 802 | PRODUCT_NAME = Decodable; 803 | SDKROOT = macosx; 804 | SKIP_INSTALL = YES; 805 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 806 | }; 807 | name = Release; 808 | }; 809 | 17FB810C1B530FED0012F106 /* Debug */ = { 810 | isa = XCBuildConfiguration; 811 | buildSettings = { 812 | APPLICATION_EXTENSION_API_ONLY = NO; 813 | COMBINE_HIDPI_IMAGES = YES; 814 | ENABLE_BITCODE = NO; 815 | INFOPLIST_FILE = Tests/Info.plist; 816 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 817 | MACOSX_DEPLOYMENT_TARGET = 10.10; 818 | PRODUCT_BUNDLE_IDENTIFIER = anviking.DecodableTests; 819 | PRODUCT_NAME = "$(TARGET_NAME)"; 820 | SDKROOT = macosx; 821 | }; 822 | name = Debug; 823 | }; 824 | 17FB810D1B530FED0012F106 /* Release */ = { 825 | isa = XCBuildConfiguration; 826 | buildSettings = { 827 | APPLICATION_EXTENSION_API_ONLY = NO; 828 | COMBINE_HIDPI_IMAGES = YES; 829 | ENABLE_BITCODE = NO; 830 | INFOPLIST_FILE = Tests/Info.plist; 831 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 832 | MACOSX_DEPLOYMENT_TARGET = 10.10; 833 | PRODUCT_BUNDLE_IDENTIFIER = anviking.DecodableTests; 834 | PRODUCT_NAME = "$(TARGET_NAME)"; 835 | SDKROOT = macosx; 836 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 837 | }; 838 | name = Release; 839 | }; 840 | 57FCDE631BA283C900130C48 /* Debug */ = { 841 | isa = XCBuildConfiguration; 842 | buildSettings = { 843 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 844 | DEFINES_MODULE = YES; 845 | DYLIB_COMPATIBILITY_VERSION = 1; 846 | DYLIB_CURRENT_VERSION = 1; 847 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 848 | INFOPLIST_FILE = Sources/Info.plist; 849 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 850 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 851 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 852 | PRODUCT_NAME = Decodable; 853 | SDKROOT = appletvos; 854 | SKIP_INSTALL = YES; 855 | TARGETED_DEVICE_FAMILY = 3; 856 | }; 857 | name = Debug; 858 | }; 859 | 57FCDE641BA283C900130C48 /* Release */ = { 860 | isa = XCBuildConfiguration; 861 | buildSettings = { 862 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 863 | DEFINES_MODULE = YES; 864 | DYLIB_COMPATIBILITY_VERSION = 1; 865 | DYLIB_CURRENT_VERSION = 1; 866 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 867 | INFOPLIST_FILE = Sources/Info.plist; 868 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 869 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 870 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 871 | PRODUCT_NAME = Decodable; 872 | SDKROOT = appletvos; 873 | SKIP_INSTALL = YES; 874 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 875 | TARGETED_DEVICE_FAMILY = 3; 876 | }; 877 | name = Release; 878 | }; 879 | 8FE7B5741B4C9FB900837609 /* Debug */ = { 880 | isa = XCBuildConfiguration; 881 | buildSettings = { 882 | ALWAYS_SEARCH_USER_PATHS = NO; 883 | APPLICATION_EXTENSION_API_ONLY = YES; 884 | BITCODE_GENERATION_MODE = bitcode; 885 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 886 | CLANG_CXX_LIBRARY = "libc++"; 887 | CLANG_ENABLE_MODULES = YES; 888 | CLANG_ENABLE_OBJC_ARC = YES; 889 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 890 | CLANG_WARN_BOOL_CONVERSION = YES; 891 | CLANG_WARN_COMMA = YES; 892 | CLANG_WARN_CONSTANT_CONVERSION = YES; 893 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 894 | CLANG_WARN_EMPTY_BODY = YES; 895 | CLANG_WARN_ENUM_CONVERSION = YES; 896 | CLANG_WARN_INFINITE_RECURSION = YES; 897 | CLANG_WARN_INT_CONVERSION = YES; 898 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 899 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 900 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 901 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 902 | CLANG_WARN_STRICT_PROTOTYPES = YES; 903 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 904 | CLANG_WARN_UNREACHABLE_CODE = YES; 905 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 906 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 907 | COPY_PHASE_STRIP = NO; 908 | CURRENT_PROJECT_VERSION = 1; 909 | DEBUG_INFORMATION_FORMAT = dwarf; 910 | ENABLE_BITCODE = YES; 911 | ENABLE_STRICT_OBJC_MSGSEND = YES; 912 | ENABLE_TESTABILITY = YES; 913 | GCC_C_LANGUAGE_STANDARD = gnu99; 914 | GCC_DYNAMIC_NO_PIC = NO; 915 | GCC_NO_COMMON_BLOCKS = YES; 916 | GCC_OPTIMIZATION_LEVEL = fast; 917 | GCC_PREPROCESSOR_DEFINITIONS = ( 918 | "DEBUG=1", 919 | "$(inherited)", 920 | ); 921 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 922 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 923 | GCC_WARN_UNDECLARED_SELECTOR = YES; 924 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 925 | GCC_WARN_UNUSED_FUNCTION = YES; 926 | GCC_WARN_UNUSED_VARIABLE = YES; 927 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 928 | MACOSX_DEPLOYMENT_TARGET = 10.10; 929 | MTL_ENABLE_DEBUG_INFO = YES; 930 | ONLY_ACTIVE_ARCH = YES; 931 | SDKROOT = iphoneos; 932 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 933 | SWIFT_VERSION = 3.0; 934 | TARGETED_DEVICE_FAMILY = "1,2"; 935 | TVOS_DEPLOYMENT_TARGET = 9.0; 936 | VERSIONING_SYSTEM = "apple-generic"; 937 | VERSION_INFO_PREFIX = ""; 938 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 939 | }; 940 | name = Debug; 941 | }; 942 | 8FE7B5751B4C9FB900837609 /* Release */ = { 943 | isa = XCBuildConfiguration; 944 | buildSettings = { 945 | ALWAYS_SEARCH_USER_PATHS = NO; 946 | APPLICATION_EXTENSION_API_ONLY = YES; 947 | BITCODE_GENERATION_MODE = bitcode; 948 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 949 | CLANG_CXX_LIBRARY = "libc++"; 950 | CLANG_ENABLE_MODULES = YES; 951 | CLANG_ENABLE_OBJC_ARC = YES; 952 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 953 | CLANG_WARN_BOOL_CONVERSION = YES; 954 | CLANG_WARN_COMMA = YES; 955 | CLANG_WARN_CONSTANT_CONVERSION = YES; 956 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 957 | CLANG_WARN_EMPTY_BODY = YES; 958 | CLANG_WARN_ENUM_CONVERSION = YES; 959 | CLANG_WARN_INFINITE_RECURSION = YES; 960 | CLANG_WARN_INT_CONVERSION = YES; 961 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 962 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 963 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 964 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 965 | CLANG_WARN_STRICT_PROTOTYPES = YES; 966 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 967 | CLANG_WARN_UNREACHABLE_CODE = YES; 968 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 969 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 970 | COPY_PHASE_STRIP = NO; 971 | CURRENT_PROJECT_VERSION = 1; 972 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 973 | ENABLE_BITCODE = YES; 974 | ENABLE_NS_ASSERTIONS = NO; 975 | ENABLE_STRICT_OBJC_MSGSEND = YES; 976 | GCC_C_LANGUAGE_STANDARD = gnu99; 977 | GCC_NO_COMMON_BLOCKS = YES; 978 | GCC_OPTIMIZATION_LEVEL = fast; 979 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 980 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 981 | GCC_WARN_UNDECLARED_SELECTOR = YES; 982 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 983 | GCC_WARN_UNUSED_FUNCTION = YES; 984 | GCC_WARN_UNUSED_VARIABLE = YES; 985 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 986 | MACOSX_DEPLOYMENT_TARGET = 10.10; 987 | MTL_ENABLE_DEBUG_INFO = NO; 988 | SDKROOT = iphoneos; 989 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 990 | SWIFT_VERSION = 3.0; 991 | TARGETED_DEVICE_FAMILY = "1,2"; 992 | TVOS_DEPLOYMENT_TARGET = 9.0; 993 | VALIDATE_PRODUCT = YES; 994 | VERSIONING_SYSTEM = "apple-generic"; 995 | VERSION_INFO_PREFIX = ""; 996 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 997 | }; 998 | name = Release; 999 | }; 1000 | 8FE7B5771B4C9FB900837609 /* Debug */ = { 1001 | isa = XCBuildConfiguration; 1002 | buildSettings = { 1003 | APPLICATION_EXTENSION_API_ONLY = YES; 1004 | CLANG_ENABLE_MODULES = YES; 1005 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 1006 | DEFINES_MODULE = YES; 1007 | DYLIB_COMPATIBILITY_VERSION = 1; 1008 | DYLIB_CURRENT_VERSION = 1; 1009 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1010 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 1011 | INFOPLIST_FILE = Sources/Info.plist; 1012 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1013 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1014 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 1015 | PRODUCT_NAME = Decodable; 1016 | SKIP_INSTALL = YES; 1017 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1018 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 1019 | SWIFT_VERSION = 4.0; 1020 | }; 1021 | name = Debug; 1022 | }; 1023 | 8FE7B5781B4C9FB900837609 /* Release */ = { 1024 | isa = XCBuildConfiguration; 1025 | buildSettings = { 1026 | APPLICATION_EXTENSION_API_ONLY = YES; 1027 | CLANG_ENABLE_MODULES = YES; 1028 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 1029 | DEFINES_MODULE = YES; 1030 | DYLIB_COMPATIBILITY_VERSION = 1; 1031 | DYLIB_CURRENT_VERSION = 1; 1032 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1033 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 1034 | INFOPLIST_FILE = Sources/Info.plist; 1035 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1036 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1037 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 1038 | PRODUCT_NAME = Decodable; 1039 | SKIP_INSTALL = YES; 1040 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 1041 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 1042 | SWIFT_VERSION = 4.0; 1043 | }; 1044 | name = Release; 1045 | }; 1046 | 8FE7B57A1B4C9FB900837609 /* Debug */ = { 1047 | isa = XCBuildConfiguration; 1048 | buildSettings = { 1049 | APPLICATION_EXTENSION_API_ONLY = NO; 1050 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 1051 | INFOPLIST_FILE = Tests/Info.plist; 1052 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1053 | PRODUCT_BUNDLE_IDENTIFIER = anviking.DecodableTests; 1054 | PRODUCT_NAME = "$(TARGET_NAME)"; 1055 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 1056 | SWIFT_VERSION = 4.0; 1057 | }; 1058 | name = Debug; 1059 | }; 1060 | 8FE7B57B1B4C9FB900837609 /* Release */ = { 1061 | isa = XCBuildConfiguration; 1062 | buildSettings = { 1063 | APPLICATION_EXTENSION_API_ONLY = NO; 1064 | GCC_GENERATE_TEST_COVERAGE_FILES = NO; 1065 | INFOPLIST_FILE = Tests/Info.plist; 1066 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1067 | PRODUCT_BUNDLE_IDENTIFIER = anviking.DecodableTests; 1068 | PRODUCT_NAME = "$(TARGET_NAME)"; 1069 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 1070 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 1071 | SWIFT_VERSION = 4.0; 1072 | }; 1073 | name = Release; 1074 | }; 1075 | D0DC54741B7814D200F79CB0 /* Debug */ = { 1076 | isa = XCBuildConfiguration; 1077 | buildSettings = { 1078 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 1079 | DEFINES_MODULE = YES; 1080 | DYLIB_COMPATIBILITY_VERSION = 1; 1081 | DYLIB_CURRENT_VERSION = 1; 1082 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1083 | INFOPLIST_FILE = Sources/Info.plist; 1084 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1085 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1086 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 1087 | PRODUCT_NAME = Decodable; 1088 | SDKROOT = watchos; 1089 | SKIP_INSTALL = YES; 1090 | TARGETED_DEVICE_FAMILY = 4; 1091 | }; 1092 | name = Debug; 1093 | }; 1094 | D0DC54751B7814D200F79CB0 /* Release */ = { 1095 | isa = XCBuildConfiguration; 1096 | buildSettings = { 1097 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 1098 | DEFINES_MODULE = YES; 1099 | DYLIB_COMPATIBILITY_VERSION = 1; 1100 | DYLIB_CURRENT_VERSION = 1; 1101 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1102 | INFOPLIST_FILE = Sources/Info.plist; 1103 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1104 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1105 | PRODUCT_BUNDLE_IDENTIFIER = anviking.Decodable; 1106 | PRODUCT_NAME = Decodable; 1107 | SDKROOT = watchos; 1108 | SKIP_INSTALL = YES; 1109 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 1110 | TARGETED_DEVICE_FAMILY = 4; 1111 | }; 1112 | name = Release; 1113 | }; 1114 | /* End XCBuildConfiguration section */ 1115 | 1116 | /* Begin XCConfigurationList section */ 1117 | 17FB81081B530FED0012F106 /* Build configuration list for PBXNativeTarget "Decodable-Mac" */ = { 1118 | isa = XCConfigurationList; 1119 | buildConfigurations = ( 1120 | 17FB81091B530FED0012F106 /* Debug */, 1121 | 17FB810A1B530FED0012F106 /* Release */, 1122 | ); 1123 | defaultConfigurationIsVisible = 0; 1124 | defaultConfigurationName = Release; 1125 | }; 1126 | 17FB810B1B530FED0012F106 /* Build configuration list for PBXNativeTarget "DecodableTests-Mac" */ = { 1127 | isa = XCConfigurationList; 1128 | buildConfigurations = ( 1129 | 17FB810C1B530FED0012F106 /* Debug */, 1130 | 17FB810D1B530FED0012F106 /* Release */, 1131 | ); 1132 | defaultConfigurationIsVisible = 0; 1133 | defaultConfigurationName = Release; 1134 | }; 1135 | 57FCDE621BA283C900130C48 /* Build configuration list for PBXNativeTarget "Decodable-tvOS" */ = { 1136 | isa = XCConfigurationList; 1137 | buildConfigurations = ( 1138 | 57FCDE631BA283C900130C48 /* Debug */, 1139 | 57FCDE641BA283C900130C48 /* Release */, 1140 | ); 1141 | defaultConfigurationIsVisible = 0; 1142 | defaultConfigurationName = Release; 1143 | }; 1144 | 8FE7B55C1B4C9FB900837609 /* Build configuration list for PBXProject "Decodable" */ = { 1145 | isa = XCConfigurationList; 1146 | buildConfigurations = ( 1147 | 8FE7B5741B4C9FB900837609 /* Debug */, 1148 | 8FE7B5751B4C9FB900837609 /* Release */, 1149 | ); 1150 | defaultConfigurationIsVisible = 0; 1151 | defaultConfigurationName = Release; 1152 | }; 1153 | 8FE7B5761B4C9FB900837609 /* Build configuration list for PBXNativeTarget "Decodable-iOS" */ = { 1154 | isa = XCConfigurationList; 1155 | buildConfigurations = ( 1156 | 8FE7B5771B4C9FB900837609 /* Debug */, 1157 | 8FE7B5781B4C9FB900837609 /* Release */, 1158 | ); 1159 | defaultConfigurationIsVisible = 0; 1160 | defaultConfigurationName = Release; 1161 | }; 1162 | 8FE7B5791B4C9FB900837609 /* Build configuration list for PBXNativeTarget "DecodableTests-iOS" */ = { 1163 | isa = XCConfigurationList; 1164 | buildConfigurations = ( 1165 | 8FE7B57A1B4C9FB900837609 /* Debug */, 1166 | 8FE7B57B1B4C9FB900837609 /* Release */, 1167 | ); 1168 | defaultConfigurationIsVisible = 0; 1169 | defaultConfigurationName = Release; 1170 | }; 1171 | D0DC54761B7814D200F79CB0 /* Build configuration list for PBXNativeTarget "Decodable-watchOS" */ = { 1172 | isa = XCConfigurationList; 1173 | buildConfigurations = ( 1174 | D0DC54741B7814D200F79CB0 /* Debug */, 1175 | D0DC54751B7814D200F79CB0 /* Release */, 1176 | ); 1177 | defaultConfigurationIsVisible = 0; 1178 | defaultConfigurationName = Release; 1179 | }; 1180 | /* End XCConfigurationList section */ 1181 | }; 1182 | rootObject = 8FE7B5591B4C9FB900837609 /* Project object */; 1183 | } 1184 | --------------------------------------------------------------------------------