├── .gitignore ├── CHANGELOG.md ├── KeyedCodable.podspec ├── KeyedCodable.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── KeyedCodable-iOS.xcscheme │ ├── KeyedCodable-macOS.xcscheme │ ├── KeyedCodable-tvOS.xcscheme │ └── KeyedCodable-watchOS.xcscheme ├── KeyedCodable ├── Info.plist ├── KeyedCodable.h └── Sources │ ├── AnyKey.swift │ ├── Helpers.swift │ ├── KeyedConfig.swift │ ├── KeyedJSONDecoder.swift │ ├── KeyedJSONEncoder.swift │ ├── KeyedKey.swift │ ├── Transformers.swift │ └── ZeroDecodable.swift ├── KeyedCodableTests ├── AllKeysTests.swift ├── DecodePathTest.swift ├── ExtensionTests.swift ├── FlatTests.swift ├── Info.plist ├── InnerTests.swift ├── KeyOptionsTests.swift ├── KeyedCodableTests.swift ├── OptionalArrayElementTests.swift ├── TransformersTests.swift └── ZeroDecodableTests.swift ├── LICENSE ├── Package.swift ├── README.md └── keyedCodable.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | xcuserdata 12 | *.xccheckout 13 | *.moved-aside 14 | DerivedData 15 | *.xcuserstate 16 | # AppCode etc. 17 | .idea/ 18 | # Desktop Servies 19 | .DS_Store 20 | 21 | # Carthage 22 | Carthage/Build 23 | 24 | # Swift package manager 25 | .build/ 26 | Packages/ 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.1.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/3.1.0) 2 | 3 | #### New 4 | -SPM integration added 5 | 6 | ## [3.0.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/3.0.0) 7 | 8 | #### New 9 | - @CodedBy, @EncodedBy, @DecodedBy transformers added 10 | - @Zero fill implementation added 11 | - @Flat properties and arrays added 12 | 13 | 14 | ## [2.6.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/2.6.0) 15 | 16 | #### New 17 | - added .keyed to Codable extensions to increase readibility 18 | - deprecate Codable extensions from 2.5.0 19 | 20 | ## [2.5.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/2.5.0) 21 | 22 | #### New 23 | - added support for manual coding with keyed AnyKey 24 | - Keyed<> wrapper added 25 | - Codable extensions from/toString 26 | - Default JSON coders 27 | - valuetype support for json coding 28 | 29 | ## [2.0.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/2.0.0) 30 | 31 | #### New 32 | - redesigned way of encoding and decoding the keyed codables 33 | 34 | 35 | ## [1.2.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/1.2.0) 36 | 37 | #### New 38 | - ifPresent operators added 39 | - swift 5 migration 40 | 41 | ## [1.1.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/v1.1.0) 42 | 43 | #### Fixed 44 | - Encoder fixes for conficting inner keys 45 | - decodeIfPresent in decoder 46 | - improved UnitTests 47 | 48 | ## [1.0.0](https://github.com/dgrzeszczak/KeyedCodable/releases/tag/v1.0.0) 49 | 50 | #### Added 51 | - Initial release of KeyedCodable. 52 | -------------------------------------------------------------------------------- /KeyedCodable.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'KeyedCodable' 3 | s.version = '3.0.0' 4 | s.license = 'MIT' 5 | s.summary = 'Easy nested key mappings for swift Codable' 6 | s.description = <<-DESC 7 | KeyedCodable is an addition to swift's Codable and it's designed for automatic nested key mappings. The goal it to avoid manual implementation of Encodable/Decodable and make encoding/decoding easier, more readable, less boilerplate and what is the most important fully compatible with 'standard' Codable. 8 | DESC 9 | 10 | s.homepage = 'https://github.com/dgrzeszczak/KeyedCodable' 11 | s.authors = { 'Dariusz Grzeszczak' => 'dariusz.grzeszczak@interia.pl' } 12 | s.source = { :git => 'https://github.com/dgrzeszczak/KeyedCodable.git', :tag => s.version } 13 | 14 | s.watchos.deployment_target = '2.0' 15 | s.ios.deployment_target = '8.0' 16 | s.osx.deployment_target = '10.9' 17 | s.tvos.deployment_target = '9.0' 18 | 19 | s.swift_version = '4.1' 20 | 21 | s.pod_target_xcconfig = { 22 | 'SWIFT_VERSION' => '4.1', 23 | } 24 | 25 | s.requires_arc = true 26 | s.source_files = 'KeyedCodable/Sources/**/*.swift' 27 | end 28 | -------------------------------------------------------------------------------- /KeyedCodable.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0823925520A56B6C006C45B2 /* InnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925420A56B6C006C45B2 /* InnerTests.swift */; }; 11 | 08276C3D232AEC0C0030C9B2 /* ExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08276C3C232AEC0C0030C9B2 /* ExtensionTests.swift */; }; 12 | 0838E8E12267D5070010A7ED /* FlatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925620A56CED006C45B2 /* FlatTests.swift */; }; 13 | 0838E8EB2267D5A70010A7ED /* AllKeysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC29BB207BF444008B9726 /* AllKeysTests.swift */; }; 14 | 0889DB5E20699EA8008A0A13 /* KeyedCodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0889DB5420699EA8008A0A13 /* KeyedCodable.framework */; }; 15 | 0889DB6520699EA8008A0A13 /* KeyedCodable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0889DB5720699EA8008A0A13 /* KeyedCodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 0889DB8A20699FF0008A0A13 /* KeyedJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8920699FF0008A0A13 /* KeyedJSONEncoder.swift */; }; 17 | 0889DB8C2069A01E008A0A13 /* KeyedJSONDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8B2069A01E008A0A13 /* KeyedJSONDecoder.swift */; }; 18 | 0889DB912069A1A3008A0A13 /* AnyKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB902069A1A3008A0A13 /* AnyKey.swift */; }; 19 | 0889DB9D2069A2F5008A0A13 /* KeyedConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9C2069A2F5008A0A13 /* KeyedConfig.swift */; }; 20 | 0889DBA02069A344008A0A13 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9F2069A344008A0A13 /* Helpers.swift */; }; 21 | 088D833823490CFF00642B31 /* Transformers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833723490CFF00642B31 /* Transformers.swift */; }; 22 | 088D833923490CFF00642B31 /* Transformers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833723490CFF00642B31 /* Transformers.swift */; }; 23 | 088D833A23490CFF00642B31 /* Transformers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833723490CFF00642B31 /* Transformers.swift */; }; 24 | 088D833B23490CFF00642B31 /* Transformers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833723490CFF00642B31 /* Transformers.swift */; }; 25 | 088D833D23494A8600642B31 /* ZeroDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833C23494A8600642B31 /* ZeroDecodable.swift */; }; 26 | 088D833E23494A8600642B31 /* ZeroDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833C23494A8600642B31 /* ZeroDecodable.swift */; }; 27 | 088D833F23494A8600642B31 /* ZeroDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833C23494A8600642B31 /* ZeroDecodable.swift */; }; 28 | 088D834023494A8600642B31 /* ZeroDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D833C23494A8600642B31 /* ZeroDecodable.swift */; }; 29 | 088D83422349ECD600642B31 /* ZeroDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D83412349ECD600642B31 /* ZeroDecodableTests.swift */; }; 30 | 088D83432349ECD600642B31 /* ZeroDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D83412349ECD600642B31 /* ZeroDecodableTests.swift */; }; 31 | 088D83442349ECD600642B31 /* ZeroDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D83412349ECD600642B31 /* ZeroDecodableTests.swift */; }; 32 | 088D83462349ECF700642B31 /* TransformersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D83452349ECF700642B31 /* TransformersTests.swift */; }; 33 | 088D83472349ECF700642B31 /* TransformersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D83452349ECF700642B31 /* TransformersTests.swift */; }; 34 | 088D83482349ECF700642B31 /* TransformersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088D83452349ECF700642B31 /* TransformersTests.swift */; }; 35 | 089426FC20B1B9E300A4B6FF /* KeyedCodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 089426F320B1B9E300A4B6FF /* KeyedCodable.framework */; }; 36 | 0894271820B1BA0700A4B6FF /* KeyedCodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0894270F20B1BA0600A4B6FF /* KeyedCodable.framework */; }; 37 | 0894272620B1BA7100A4B6FF /* KeyedConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9C2069A2F5008A0A13 /* KeyedConfig.swift */; }; 38 | 0894272920B1BA7100A4B6FF /* KeyedJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8920699FF0008A0A13 /* KeyedJSONEncoder.swift */; }; 39 | 0894272A20B1BA7100A4B6FF /* KeyedJSONDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8B2069A01E008A0A13 /* KeyedJSONDecoder.swift */; }; 40 | 0894272C20B1BA7100A4B6FF /* AnyKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB902069A1A3008A0A13 /* AnyKey.swift */; }; 41 | 0894272D20B1BA7100A4B6FF /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9F2069A344008A0A13 /* Helpers.swift */; }; 42 | 0894272E20B1BA7200A4B6FF /* KeyedConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9C2069A2F5008A0A13 /* KeyedConfig.swift */; }; 43 | 0894273120B1BA7200A4B6FF /* KeyedJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8920699FF0008A0A13 /* KeyedJSONEncoder.swift */; }; 44 | 0894273220B1BA7200A4B6FF /* KeyedJSONDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8B2069A01E008A0A13 /* KeyedJSONDecoder.swift */; }; 45 | 0894273420B1BA7200A4B6FF /* AnyKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB902069A1A3008A0A13 /* AnyKey.swift */; }; 46 | 0894273520B1BA7200A4B6FF /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9F2069A344008A0A13 /* Helpers.swift */; }; 47 | 0894273E20B1BA7300A4B6FF /* KeyedConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9C2069A2F5008A0A13 /* KeyedConfig.swift */; }; 48 | 0894274120B1BA7300A4B6FF /* KeyedJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8920699FF0008A0A13 /* KeyedJSONEncoder.swift */; }; 49 | 0894274220B1BA7300A4B6FF /* KeyedJSONDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB8B2069A01E008A0A13 /* KeyedJSONDecoder.swift */; }; 50 | 0894274420B1BA7300A4B6FF /* AnyKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB902069A1A3008A0A13 /* AnyKey.swift */; }; 51 | 0894274520B1BA7300A4B6FF /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB9F2069A344008A0A13 /* Helpers.swift */; }; 52 | 0894274F20B1BA7B00A4B6FF /* KeyedCodable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0889DB5720699EA8008A0A13 /* KeyedCodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 53 | 0894275020B1BA7B00A4B6FF /* KeyedCodable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0889DB5720699EA8008A0A13 /* KeyedCodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 54 | 0894275120B1BA8300A4B6FF /* KeyedCodable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0889DB5720699EA8008A0A13 /* KeyedCodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 55 | 0894275220B1BA8E00A4B6FF /* InnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925420A56B6C006C45B2 /* InnerTests.swift */; }; 56 | 0894275320B1BA8E00A4B6FF /* FlatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925620A56CED006C45B2 /* FlatTests.swift */; }; 57 | 0894275420B1BA8E00A4B6FF /* OptionalArrayElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925820A56DB3006C45B2 /* OptionalArrayElementTests.swift */; }; 58 | 0894275520B1BA8E00A4B6FF /* AllKeysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC29BB207BF444008B9726 /* AllKeysTests.swift */; }; 59 | 0894275620B1BA8E00A4B6FF /* KeyOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925A20A56EC1006C45B2 /* KeyOptionsTests.swift */; }; 60 | 0894275720B1BA8E00A4B6FF /* KeyedCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB6220699EA8008A0A13 /* KeyedCodableTests.swift */; }; 61 | 0894275820B1BA8F00A4B6FF /* InnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925420A56B6C006C45B2 /* InnerTests.swift */; }; 62 | 0894275920B1BA8F00A4B6FF /* FlatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925620A56CED006C45B2 /* FlatTests.swift */; }; 63 | 0894275A20B1BA8F00A4B6FF /* OptionalArrayElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925820A56DB3006C45B2 /* OptionalArrayElementTests.swift */; }; 64 | 0894275B20B1BA8F00A4B6FF /* AllKeysTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DC29BB207BF444008B9726 /* AllKeysTests.swift */; }; 65 | 0894275C20B1BA8F00A4B6FF /* KeyOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925A20A56EC1006C45B2 /* KeyOptionsTests.swift */; }; 66 | 0894275D20B1BA8F00A4B6FF /* KeyedCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB6220699EA8008A0A13 /* KeyedCodableTests.swift */; }; 67 | 0899CEAA22354CAE000B7F9E /* KeyedCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0889DB6220699EA8008A0A13 /* KeyedCodableTests.swift */; }; 68 | 08DCCEE322E9F4C100C5018D /* DecodePathTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DCCEE222E9F4C100C5018D /* DecodePathTest.swift */; }; 69 | 08DCCEE422E9F4C100C5018D /* DecodePathTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DCCEE222E9F4C100C5018D /* DecodePathTest.swift */; }; 70 | 08DCCEE522E9F4C100C5018D /* DecodePathTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DCCEE222E9F4C100C5018D /* DecodePathTest.swift */; }; 71 | 08FD6E4F22746520007E4D59 /* OptionalArrayElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925820A56DB3006C45B2 /* OptionalArrayElementTests.swift */; }; 72 | 08FD6E642278C92A007E4D59 /* KeyOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0823925A20A56EC1006C45B2 /* KeyOptionsTests.swift */; }; 73 | 08FD6ED92279F5BA007E4D59 /* KeyedKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FD6ED82279F5BA007E4D59 /* KeyedKey.swift */; }; 74 | 08FD6EDA2279F5BA007E4D59 /* KeyedKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FD6ED82279F5BA007E4D59 /* KeyedKey.swift */; }; 75 | 08FD6EDB2279F5BA007E4D59 /* KeyedKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FD6ED82279F5BA007E4D59 /* KeyedKey.swift */; }; 76 | 08FD6EDC2279F5BA007E4D59 /* KeyedKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FD6ED82279F5BA007E4D59 /* KeyedKey.swift */; }; 77 | /* End PBXBuildFile section */ 78 | 79 | /* Begin PBXContainerItemProxy section */ 80 | 0889DB5F20699EA8008A0A13 /* PBXContainerItemProxy */ = { 81 | isa = PBXContainerItemProxy; 82 | containerPortal = 0889DB4B20699EA8008A0A13 /* Project object */; 83 | proxyType = 1; 84 | remoteGlobalIDString = 0889DB5320699EA8008A0A13; 85 | remoteInfo = KeyedCoder; 86 | }; 87 | 089426FD20B1B9E300A4B6FF /* PBXContainerItemProxy */ = { 88 | isa = PBXContainerItemProxy; 89 | containerPortal = 0889DB4B20699EA8008A0A13 /* Project object */; 90 | proxyType = 1; 91 | remoteGlobalIDString = 089426F220B1B9E300A4B6FF; 92 | remoteInfo = "KeyedCodable-tvOS"; 93 | }; 94 | 0894271920B1BA0700A4B6FF /* PBXContainerItemProxy */ = { 95 | isa = PBXContainerItemProxy; 96 | containerPortal = 0889DB4B20699EA8008A0A13 /* Project object */; 97 | proxyType = 1; 98 | remoteGlobalIDString = 0894270E20B1BA0600A4B6FF; 99 | remoteInfo = "KeyedCodable-macOS"; 100 | }; 101 | /* End PBXContainerItemProxy section */ 102 | 103 | /* Begin PBXFileReference section */ 104 | 0807903020A9062C0023C651 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 105 | 0823925420A56B6C006C45B2 /* InnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTests.swift; sourceTree = ""; }; 106 | 0823925620A56CED006C45B2 /* FlatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlatTests.swift; sourceTree = ""; }; 107 | 0823925820A56DB3006C45B2 /* OptionalArrayElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalArrayElementTests.swift; sourceTree = ""; }; 108 | 0823925A20A56EC1006C45B2 /* KeyOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyOptionsTests.swift; sourceTree = ""; }; 109 | 08276C3C232AEC0C0030C9B2 /* ExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionTests.swift; sourceTree = ""; }; 110 | 086C497320BAEBFA008DB20C /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; }; 111 | 0889DB5420699EA8008A0A13 /* KeyedCodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeyedCodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 112 | 0889DB5720699EA8008A0A13 /* KeyedCodable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyedCodable.h; sourceTree = ""; }; 113 | 0889DB5820699EA8008A0A13 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 114 | 0889DB5D20699EA8008A0A13 /* KeyedCodableTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "KeyedCodableTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 115 | 0889DB6220699EA8008A0A13 /* KeyedCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedCodableTests.swift; sourceTree = ""; }; 116 | 0889DB6420699EA8008A0A13 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 117 | 0889DB8920699FF0008A0A13 /* KeyedJSONEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedJSONEncoder.swift; sourceTree = ""; }; 118 | 0889DB8B2069A01E008A0A13 /* KeyedJSONDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedJSONDecoder.swift; sourceTree = ""; }; 119 | 0889DB902069A1A3008A0A13 /* AnyKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyKey.swift; sourceTree = ""; }; 120 | 0889DB9C2069A2F5008A0A13 /* KeyedConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedConfig.swift; sourceTree = ""; }; 121 | 0889DB9F2069A344008A0A13 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; 122 | 088D833723490CFF00642B31 /* Transformers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformers.swift; sourceTree = ""; }; 123 | 088D833C23494A8600642B31 /* ZeroDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZeroDecodable.swift; sourceTree = ""; }; 124 | 088D83412349ECD600642B31 /* ZeroDecodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZeroDecodableTests.swift; sourceTree = ""; }; 125 | 088D83452349ECF700642B31 /* TransformersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformersTests.swift; sourceTree = ""; }; 126 | 089426E620B1B87900A4B6FF /* KeyedCodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeyedCodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 127 | 089426F320B1B9E300A4B6FF /* KeyedCodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeyedCodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 128 | 089426FB20B1B9E300A4B6FF /* KeyedCodable-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "KeyedCodable-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 129 | 0894270F20B1BA0600A4B6FF /* KeyedCodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeyedCodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 130 | 0894271720B1BA0700A4B6FF /* KeyedCodable-macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "KeyedCodable-macOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 131 | 08942A0320B1F4C700A4B6FF /* KeyedCodable.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = KeyedCodable.podspec; sourceTree = SOURCE_ROOT; }; 132 | 08DC29BB207BF444008B9726 /* AllKeysTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllKeysTests.swift; sourceTree = ""; }; 133 | 08DCCEE222E9F4C100C5018D /* DecodePathTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodePathTest.swift; sourceTree = ""; }; 134 | 08FD6ED82279F5BA007E4D59 /* KeyedKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedKey.swift; sourceTree = ""; }; 135 | 3720A4FF23F1A0AA000DE8E6 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; }; 136 | /* End PBXFileReference section */ 137 | 138 | /* Begin PBXFrameworksBuildPhase section */ 139 | 0889DB5020699EA8008A0A13 /* Frameworks */ = { 140 | isa = PBXFrameworksBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | 0889DB5A20699EA8008A0A13 /* Frameworks */ = { 147 | isa = PBXFrameworksBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | 0889DB5E20699EA8008A0A13 /* KeyedCodable.framework in Frameworks */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | 089426E220B1B87900A4B6FF /* Frameworks */ = { 155 | isa = PBXFrameworksBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | 089426EF20B1B9E300A4B6FF /* Frameworks */ = { 162 | isa = PBXFrameworksBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | 089426F820B1B9E300A4B6FF /* Frameworks */ = { 169 | isa = PBXFrameworksBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 089426FC20B1B9E300A4B6FF /* KeyedCodable.framework in Frameworks */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | 0894270B20B1BA0600A4B6FF /* Frameworks */ = { 177 | isa = PBXFrameworksBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | 0894271420B1BA0700A4B6FF /* Frameworks */ = { 184 | isa = PBXFrameworksBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 0894271820B1BA0700A4B6FF /* KeyedCodable.framework in Frameworks */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXFrameworksBuildPhase section */ 192 | 193 | /* Begin PBXGroup section */ 194 | 0889DB4A20699EA8008A0A13 = { 195 | isa = PBXGroup; 196 | children = ( 197 | 0889DB5620699EA8008A0A13 /* KeyedCodable */, 198 | 0889DB6120699EA8008A0A13 /* KeyedCodableTests */, 199 | 0889DB5520699EA8008A0A13 /* Products */, 200 | ); 201 | sourceTree = ""; 202 | }; 203 | 0889DB5520699EA8008A0A13 /* Products */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 0889DB5420699EA8008A0A13 /* KeyedCodable.framework */, 207 | 0889DB5D20699EA8008A0A13 /* KeyedCodableTests-iOS.xctest */, 208 | 089426E620B1B87900A4B6FF /* KeyedCodable.framework */, 209 | 089426F320B1B9E300A4B6FF /* KeyedCodable.framework */, 210 | 089426FB20B1B9E300A4B6FF /* KeyedCodable-tvOSTests.xctest */, 211 | 0894270F20B1BA0600A4B6FF /* KeyedCodable.framework */, 212 | 0894271720B1BA0700A4B6FF /* KeyedCodable-macOSTests.xctest */, 213 | ); 214 | name = Products; 215 | sourceTree = ""; 216 | }; 217 | 0889DB5620699EA8008A0A13 /* KeyedCodable */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | 3720A4FF23F1A0AA000DE8E6 /* Package.swift */, 221 | 0889DB8820699FB6008A0A13 /* Sources */, 222 | 0889DB5720699EA8008A0A13 /* KeyedCodable.h */, 223 | 0889DB5820699EA8008A0A13 /* Info.plist */, 224 | ); 225 | path = KeyedCodable; 226 | sourceTree = ""; 227 | }; 228 | 0889DB6120699EA8008A0A13 /* KeyedCodableTests */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | 0823925420A56B6C006C45B2 /* InnerTests.swift */, 232 | 0823925620A56CED006C45B2 /* FlatTests.swift */, 233 | 0823925820A56DB3006C45B2 /* OptionalArrayElementTests.swift */, 234 | 08DC29BB207BF444008B9726 /* AllKeysTests.swift */, 235 | 0823925A20A56EC1006C45B2 /* KeyOptionsTests.swift */, 236 | 0889DB6220699EA8008A0A13 /* KeyedCodableTests.swift */, 237 | 08DCCEE222E9F4C100C5018D /* DecodePathTest.swift */, 238 | 08276C3C232AEC0C0030C9B2 /* ExtensionTests.swift */, 239 | 088D83412349ECD600642B31 /* ZeroDecodableTests.swift */, 240 | 088D83452349ECF700642B31 /* TransformersTests.swift */, 241 | 086C497320BAEBFA008DB20C /* CHANGELOG.md */, 242 | 0807903020A9062C0023C651 /* README.md */, 243 | 0889DB6420699EA8008A0A13 /* Info.plist */, 244 | 08942A0320B1F4C700A4B6FF /* KeyedCodable.podspec */, 245 | ); 246 | path = KeyedCodableTests; 247 | sourceTree = ""; 248 | }; 249 | 0889DB8820699FB6008A0A13 /* Sources */ = { 250 | isa = PBXGroup; 251 | children = ( 252 | 08FD6ED82279F5BA007E4D59 /* KeyedKey.swift */, 253 | 0889DB9C2069A2F5008A0A13 /* KeyedConfig.swift */, 254 | 0889DB8920699FF0008A0A13 /* KeyedJSONEncoder.swift */, 255 | 0889DB8B2069A01E008A0A13 /* KeyedJSONDecoder.swift */, 256 | 088D833723490CFF00642B31 /* Transformers.swift */, 257 | 088D833C23494A8600642B31 /* ZeroDecodable.swift */, 258 | 0889DB902069A1A3008A0A13 /* AnyKey.swift */, 259 | 0889DB9F2069A344008A0A13 /* Helpers.swift */, 260 | ); 261 | path = Sources; 262 | sourceTree = ""; 263 | }; 264 | /* End PBXGroup section */ 265 | 266 | /* Begin PBXHeadersBuildPhase section */ 267 | 0889DB5120699EA8008A0A13 /* Headers */ = { 268 | isa = PBXHeadersBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 0889DB6520699EA8008A0A13 /* KeyedCodable.h in Headers */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | 089426E320B1B87900A4B6FF /* Headers */ = { 276 | isa = PBXHeadersBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | 0894274F20B1BA7B00A4B6FF /* KeyedCodable.h in Headers */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | 089426F020B1B9E300A4B6FF /* Headers */ = { 284 | isa = PBXHeadersBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 0894275020B1BA7B00A4B6FF /* KeyedCodable.h in Headers */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | 0894270C20B1BA0600A4B6FF /* Headers */ = { 292 | isa = PBXHeadersBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | 0894275120B1BA8300A4B6FF /* KeyedCodable.h in Headers */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | /* End PBXHeadersBuildPhase section */ 300 | 301 | /* Begin PBXNativeTarget section */ 302 | 0889DB5320699EA8008A0A13 /* KeyedCodable-iOS */ = { 303 | isa = PBXNativeTarget; 304 | buildConfigurationList = 0889DB6820699EA8008A0A13 /* Build configuration list for PBXNativeTarget "KeyedCodable-iOS" */; 305 | buildPhases = ( 306 | 0889DB4F20699EA8008A0A13 /* Sources */, 307 | 0889DB5020699EA8008A0A13 /* Frameworks */, 308 | 0889DB5120699EA8008A0A13 /* Headers */, 309 | 0889DB5220699EA8008A0A13 /* Resources */, 310 | ); 311 | buildRules = ( 312 | ); 313 | dependencies = ( 314 | ); 315 | name = "KeyedCodable-iOS"; 316 | productName = KeyedCoder; 317 | productReference = 0889DB5420699EA8008A0A13 /* KeyedCodable.framework */; 318 | productType = "com.apple.product-type.framework"; 319 | }; 320 | 0889DB5C20699EA8008A0A13 /* KeyedCodableTests-iOS */ = { 321 | isa = PBXNativeTarget; 322 | buildConfigurationList = 0889DB6B20699EA8008A0A13 /* Build configuration list for PBXNativeTarget "KeyedCodableTests-iOS" */; 323 | buildPhases = ( 324 | 0889DB5920699EA8008A0A13 /* Sources */, 325 | 0889DB5A20699EA8008A0A13 /* Frameworks */, 326 | 0889DB5B20699EA8008A0A13 /* Resources */, 327 | ); 328 | buildRules = ( 329 | ); 330 | dependencies = ( 331 | 0889DB6020699EA8008A0A13 /* PBXTargetDependency */, 332 | ); 333 | name = "KeyedCodableTests-iOS"; 334 | productName = KeyedCoderTests; 335 | productReference = 0889DB5D20699EA8008A0A13 /* KeyedCodableTests-iOS.xctest */; 336 | productType = "com.apple.product-type.bundle.unit-test"; 337 | }; 338 | 089426E520B1B87900A4B6FF /* KeyedCodable-watchOS */ = { 339 | isa = PBXNativeTarget; 340 | buildConfigurationList = 089426EB20B1B87900A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-watchOS" */; 341 | buildPhases = ( 342 | 089426E120B1B87900A4B6FF /* Sources */, 343 | 089426E220B1B87900A4B6FF /* Frameworks */, 344 | 089426E320B1B87900A4B6FF /* Headers */, 345 | 089426E420B1B87900A4B6FF /* Resources */, 346 | ); 347 | buildRules = ( 348 | ); 349 | dependencies = ( 350 | ); 351 | name = "KeyedCodable-watchOS"; 352 | productName = "KeyedCodable-watchOS"; 353 | productReference = 089426E620B1B87900A4B6FF /* KeyedCodable.framework */; 354 | productType = "com.apple.product-type.framework"; 355 | }; 356 | 089426F220B1B9E300A4B6FF /* KeyedCodable-tvOS */ = { 357 | isa = PBXNativeTarget; 358 | buildConfigurationList = 0894270420B1B9E300A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-tvOS" */; 359 | buildPhases = ( 360 | 089426EE20B1B9E300A4B6FF /* Sources */, 361 | 089426EF20B1B9E300A4B6FF /* Frameworks */, 362 | 089426F020B1B9E300A4B6FF /* Headers */, 363 | 089426F120B1B9E300A4B6FF /* Resources */, 364 | ); 365 | buildRules = ( 366 | ); 367 | dependencies = ( 368 | ); 369 | name = "KeyedCodable-tvOS"; 370 | productName = "KeyedCodable-tvOS"; 371 | productReference = 089426F320B1B9E300A4B6FF /* KeyedCodable.framework */; 372 | productType = "com.apple.product-type.framework"; 373 | }; 374 | 089426FA20B1B9E300A4B6FF /* KeyedCodable-tvOSTests */ = { 375 | isa = PBXNativeTarget; 376 | buildConfigurationList = 0894270720B1B9E300A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-tvOSTests" */; 377 | buildPhases = ( 378 | 089426F720B1B9E300A4B6FF /* Sources */, 379 | 089426F820B1B9E300A4B6FF /* Frameworks */, 380 | 089426F920B1B9E300A4B6FF /* Resources */, 381 | ); 382 | buildRules = ( 383 | ); 384 | dependencies = ( 385 | 089426FE20B1B9E300A4B6FF /* PBXTargetDependency */, 386 | ); 387 | name = "KeyedCodable-tvOSTests"; 388 | productName = "KeyedCodable-tvOSTests"; 389 | productReference = 089426FB20B1B9E300A4B6FF /* KeyedCodable-tvOSTests.xctest */; 390 | productType = "com.apple.product-type.bundle.unit-test"; 391 | }; 392 | 0894270E20B1BA0600A4B6FF /* KeyedCodable-macOS */ = { 393 | isa = PBXNativeTarget; 394 | buildConfigurationList = 0894272020B1BA0700A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-macOS" */; 395 | buildPhases = ( 396 | 0894270A20B1BA0600A4B6FF /* Sources */, 397 | 0894270B20B1BA0600A4B6FF /* Frameworks */, 398 | 0894270C20B1BA0600A4B6FF /* Headers */, 399 | 0894270D20B1BA0600A4B6FF /* Resources */, 400 | ); 401 | buildRules = ( 402 | ); 403 | dependencies = ( 404 | ); 405 | name = "KeyedCodable-macOS"; 406 | productName = "KeyedCodable-macOS"; 407 | productReference = 0894270F20B1BA0600A4B6FF /* KeyedCodable.framework */; 408 | productType = "com.apple.product-type.framework"; 409 | }; 410 | 0894271620B1BA0700A4B6FF /* KeyedCodable-macOSTests */ = { 411 | isa = PBXNativeTarget; 412 | buildConfigurationList = 0894272320B1BA0700A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-macOSTests" */; 413 | buildPhases = ( 414 | 0894271320B1BA0700A4B6FF /* Sources */, 415 | 0894271420B1BA0700A4B6FF /* Frameworks */, 416 | 0894271520B1BA0700A4B6FF /* Resources */, 417 | ); 418 | buildRules = ( 419 | ); 420 | dependencies = ( 421 | 0894271A20B1BA0700A4B6FF /* PBXTargetDependency */, 422 | ); 423 | name = "KeyedCodable-macOSTests"; 424 | productName = "KeyedCodable-macOSTests"; 425 | productReference = 0894271720B1BA0700A4B6FF /* KeyedCodable-macOSTests.xctest */; 426 | productType = "com.apple.product-type.bundle.unit-test"; 427 | }; 428 | /* End PBXNativeTarget section */ 429 | 430 | /* Begin PBXProject section */ 431 | 0889DB4B20699EA8008A0A13 /* Project object */ = { 432 | isa = PBXProject; 433 | attributes = { 434 | LastSwiftUpdateCheck = 0930; 435 | LastUpgradeCheck = 0930; 436 | TargetAttributes = { 437 | 0889DB5320699EA8008A0A13 = { 438 | CreatedOnToolsVersion = 9.2; 439 | LastSwiftMigration = 1020; 440 | ProvisioningStyle = Automatic; 441 | }; 442 | 0889DB5C20699EA8008A0A13 = { 443 | CreatedOnToolsVersion = 9.2; 444 | LastSwiftMigration = 1020; 445 | ProvisioningStyle = Automatic; 446 | }; 447 | 089426E520B1B87900A4B6FF = { 448 | CreatedOnToolsVersion = 9.3; 449 | ProvisioningStyle = Automatic; 450 | }; 451 | 089426F220B1B9E300A4B6FF = { 452 | CreatedOnToolsVersion = 9.3; 453 | ProvisioningStyle = Automatic; 454 | }; 455 | 089426FA20B1B9E300A4B6FF = { 456 | CreatedOnToolsVersion = 9.3; 457 | ProvisioningStyle = Automatic; 458 | }; 459 | 0894270E20B1BA0600A4B6FF = { 460 | CreatedOnToolsVersion = 9.3; 461 | ProvisioningStyle = Automatic; 462 | }; 463 | 0894271620B1BA0700A4B6FF = { 464 | CreatedOnToolsVersion = 9.3; 465 | ProvisioningStyle = Automatic; 466 | }; 467 | }; 468 | }; 469 | buildConfigurationList = 0889DB4E20699EA8008A0A13 /* Build configuration list for PBXProject "KeyedCodable" */; 470 | compatibilityVersion = "Xcode 8.0"; 471 | developmentRegion = en; 472 | hasScannedForEncodings = 0; 473 | knownRegions = ( 474 | en, 475 | Base, 476 | ); 477 | mainGroup = 0889DB4A20699EA8008A0A13; 478 | productRefGroup = 0889DB5520699EA8008A0A13 /* Products */; 479 | projectDirPath = ""; 480 | projectRoot = ""; 481 | targets = ( 482 | 0889DB5320699EA8008A0A13 /* KeyedCodable-iOS */, 483 | 089426E520B1B87900A4B6FF /* KeyedCodable-watchOS */, 484 | 089426F220B1B9E300A4B6FF /* KeyedCodable-tvOS */, 485 | 0894270E20B1BA0600A4B6FF /* KeyedCodable-macOS */, 486 | 0889DB5C20699EA8008A0A13 /* KeyedCodableTests-iOS */, 487 | 089426FA20B1B9E300A4B6FF /* KeyedCodable-tvOSTests */, 488 | 0894271620B1BA0700A4B6FF /* KeyedCodable-macOSTests */, 489 | ); 490 | }; 491 | /* End PBXProject section */ 492 | 493 | /* Begin PBXResourcesBuildPhase section */ 494 | 0889DB5220699EA8008A0A13 /* Resources */ = { 495 | isa = PBXResourcesBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | ); 499 | runOnlyForDeploymentPostprocessing = 0; 500 | }; 501 | 0889DB5B20699EA8008A0A13 /* Resources */ = { 502 | isa = PBXResourcesBuildPhase; 503 | buildActionMask = 2147483647; 504 | files = ( 505 | ); 506 | runOnlyForDeploymentPostprocessing = 0; 507 | }; 508 | 089426E420B1B87900A4B6FF /* Resources */ = { 509 | isa = PBXResourcesBuildPhase; 510 | buildActionMask = 2147483647; 511 | files = ( 512 | ); 513 | runOnlyForDeploymentPostprocessing = 0; 514 | }; 515 | 089426F120B1B9E300A4B6FF /* Resources */ = { 516 | isa = PBXResourcesBuildPhase; 517 | buildActionMask = 2147483647; 518 | files = ( 519 | ); 520 | runOnlyForDeploymentPostprocessing = 0; 521 | }; 522 | 089426F920B1B9E300A4B6FF /* Resources */ = { 523 | isa = PBXResourcesBuildPhase; 524 | buildActionMask = 2147483647; 525 | files = ( 526 | ); 527 | runOnlyForDeploymentPostprocessing = 0; 528 | }; 529 | 0894270D20B1BA0600A4B6FF /* Resources */ = { 530 | isa = PBXResourcesBuildPhase; 531 | buildActionMask = 2147483647; 532 | files = ( 533 | ); 534 | runOnlyForDeploymentPostprocessing = 0; 535 | }; 536 | 0894271520B1BA0700A4B6FF /* Resources */ = { 537 | isa = PBXResourcesBuildPhase; 538 | buildActionMask = 2147483647; 539 | files = ( 540 | ); 541 | runOnlyForDeploymentPostprocessing = 0; 542 | }; 543 | /* End PBXResourcesBuildPhase section */ 544 | 545 | /* Begin PBXSourcesBuildPhase section */ 546 | 0889DB4F20699EA8008A0A13 /* Sources */ = { 547 | isa = PBXSourcesBuildPhase; 548 | buildActionMask = 2147483647; 549 | files = ( 550 | 088D833823490CFF00642B31 /* Transformers.swift in Sources */, 551 | 088D833D23494A8600642B31 /* ZeroDecodable.swift in Sources */, 552 | 0889DB8C2069A01E008A0A13 /* KeyedJSONDecoder.swift in Sources */, 553 | 08FD6ED92279F5BA007E4D59 /* KeyedKey.swift in Sources */, 554 | 0889DB9D2069A2F5008A0A13 /* KeyedConfig.swift in Sources */, 555 | 0889DBA02069A344008A0A13 /* Helpers.swift in Sources */, 556 | 0889DB912069A1A3008A0A13 /* AnyKey.swift in Sources */, 557 | 0889DB8A20699FF0008A0A13 /* KeyedJSONEncoder.swift in Sources */, 558 | ); 559 | runOnlyForDeploymentPostprocessing = 0; 560 | }; 561 | 0889DB5920699EA8008A0A13 /* Sources */ = { 562 | isa = PBXSourcesBuildPhase; 563 | buildActionMask = 2147483647; 564 | files = ( 565 | 08FD6E642278C92A007E4D59 /* KeyOptionsTests.swift in Sources */, 566 | 0838E8EB2267D5A70010A7ED /* AllKeysTests.swift in Sources */, 567 | 08DCCEE322E9F4C100C5018D /* DecodePathTest.swift in Sources */, 568 | 088D83422349ECD600642B31 /* ZeroDecodableTests.swift in Sources */, 569 | 0838E8E12267D5070010A7ED /* FlatTests.swift in Sources */, 570 | 0899CEAA22354CAE000B7F9E /* KeyedCodableTests.swift in Sources */, 571 | 08FD6E4F22746520007E4D59 /* OptionalArrayElementTests.swift in Sources */, 572 | 088D83462349ECF700642B31 /* TransformersTests.swift in Sources */, 573 | 0823925520A56B6C006C45B2 /* InnerTests.swift in Sources */, 574 | 08276C3D232AEC0C0030C9B2 /* ExtensionTests.swift in Sources */, 575 | ); 576 | runOnlyForDeploymentPostprocessing = 0; 577 | }; 578 | 089426E120B1B87900A4B6FF /* Sources */ = { 579 | isa = PBXSourcesBuildPhase; 580 | buildActionMask = 2147483647; 581 | files = ( 582 | 088D833923490CFF00642B31 /* Transformers.swift in Sources */, 583 | 088D833E23494A8600642B31 /* ZeroDecodable.swift in Sources */, 584 | 0894272C20B1BA7100A4B6FF /* AnyKey.swift in Sources */, 585 | 08FD6EDA2279F5BA007E4D59 /* KeyedKey.swift in Sources */, 586 | 0894272620B1BA7100A4B6FF /* KeyedConfig.swift in Sources */, 587 | 0894272D20B1BA7100A4B6FF /* Helpers.swift in Sources */, 588 | 0894272A20B1BA7100A4B6FF /* KeyedJSONDecoder.swift in Sources */, 589 | 0894272920B1BA7100A4B6FF /* KeyedJSONEncoder.swift in Sources */, 590 | ); 591 | runOnlyForDeploymentPostprocessing = 0; 592 | }; 593 | 089426EE20B1B9E300A4B6FF /* Sources */ = { 594 | isa = PBXSourcesBuildPhase; 595 | buildActionMask = 2147483647; 596 | files = ( 597 | 088D833A23490CFF00642B31 /* Transformers.swift in Sources */, 598 | 088D833F23494A8600642B31 /* ZeroDecodable.swift in Sources */, 599 | 0894273420B1BA7200A4B6FF /* AnyKey.swift in Sources */, 600 | 08FD6EDB2279F5BA007E4D59 /* KeyedKey.swift in Sources */, 601 | 0894272E20B1BA7200A4B6FF /* KeyedConfig.swift in Sources */, 602 | 0894273520B1BA7200A4B6FF /* Helpers.swift in Sources */, 603 | 0894273220B1BA7200A4B6FF /* KeyedJSONDecoder.swift in Sources */, 604 | 0894273120B1BA7200A4B6FF /* KeyedJSONEncoder.swift in Sources */, 605 | ); 606 | runOnlyForDeploymentPostprocessing = 0; 607 | }; 608 | 089426F720B1B9E300A4B6FF /* Sources */ = { 609 | isa = PBXSourcesBuildPhase; 610 | buildActionMask = 2147483647; 611 | files = ( 612 | 0894275220B1BA8E00A4B6FF /* InnerTests.swift in Sources */, 613 | 088D83432349ECD600642B31 /* ZeroDecodableTests.swift in Sources */, 614 | 0894275520B1BA8E00A4B6FF /* AllKeysTests.swift in Sources */, 615 | 08DCCEE422E9F4C100C5018D /* DecodePathTest.swift in Sources */, 616 | 0894275420B1BA8E00A4B6FF /* OptionalArrayElementTests.swift in Sources */, 617 | 0894275720B1BA8E00A4B6FF /* KeyedCodableTests.swift in Sources */, 618 | 088D83472349ECF700642B31 /* TransformersTests.swift in Sources */, 619 | 0894275620B1BA8E00A4B6FF /* KeyOptionsTests.swift in Sources */, 620 | 0894275320B1BA8E00A4B6FF /* FlatTests.swift in Sources */, 621 | ); 622 | runOnlyForDeploymentPostprocessing = 0; 623 | }; 624 | 0894270A20B1BA0600A4B6FF /* Sources */ = { 625 | isa = PBXSourcesBuildPhase; 626 | buildActionMask = 2147483647; 627 | files = ( 628 | 088D833B23490CFF00642B31 /* Transformers.swift in Sources */, 629 | 088D834023494A8600642B31 /* ZeroDecodable.swift in Sources */, 630 | 0894274420B1BA7300A4B6FF /* AnyKey.swift in Sources */, 631 | 08FD6EDC2279F5BA007E4D59 /* KeyedKey.swift in Sources */, 632 | 0894273E20B1BA7300A4B6FF /* KeyedConfig.swift in Sources */, 633 | 0894274520B1BA7300A4B6FF /* Helpers.swift in Sources */, 634 | 0894274220B1BA7300A4B6FF /* KeyedJSONDecoder.swift in Sources */, 635 | 0894274120B1BA7300A4B6FF /* KeyedJSONEncoder.swift in Sources */, 636 | ); 637 | runOnlyForDeploymentPostprocessing = 0; 638 | }; 639 | 0894271320B1BA0700A4B6FF /* Sources */ = { 640 | isa = PBXSourcesBuildPhase; 641 | buildActionMask = 2147483647; 642 | files = ( 643 | 0894275820B1BA8F00A4B6FF /* InnerTests.swift in Sources */, 644 | 088D83442349ECD600642B31 /* ZeroDecodableTests.swift in Sources */, 645 | 0894275B20B1BA8F00A4B6FF /* AllKeysTests.swift in Sources */, 646 | 08DCCEE522E9F4C100C5018D /* DecodePathTest.swift in Sources */, 647 | 0894275A20B1BA8F00A4B6FF /* OptionalArrayElementTests.swift in Sources */, 648 | 0894275D20B1BA8F00A4B6FF /* KeyedCodableTests.swift in Sources */, 649 | 088D83482349ECF700642B31 /* TransformersTests.swift in Sources */, 650 | 0894275C20B1BA8F00A4B6FF /* KeyOptionsTests.swift in Sources */, 651 | 0894275920B1BA8F00A4B6FF /* FlatTests.swift in Sources */, 652 | ); 653 | runOnlyForDeploymentPostprocessing = 0; 654 | }; 655 | /* End PBXSourcesBuildPhase section */ 656 | 657 | /* Begin PBXTargetDependency section */ 658 | 0889DB6020699EA8008A0A13 /* PBXTargetDependency */ = { 659 | isa = PBXTargetDependency; 660 | target = 0889DB5320699EA8008A0A13 /* KeyedCodable-iOS */; 661 | targetProxy = 0889DB5F20699EA8008A0A13 /* PBXContainerItemProxy */; 662 | }; 663 | 089426FE20B1B9E300A4B6FF /* PBXTargetDependency */ = { 664 | isa = PBXTargetDependency; 665 | target = 089426F220B1B9E300A4B6FF /* KeyedCodable-tvOS */; 666 | targetProxy = 089426FD20B1B9E300A4B6FF /* PBXContainerItemProxy */; 667 | }; 668 | 0894271A20B1BA0700A4B6FF /* PBXTargetDependency */ = { 669 | isa = PBXTargetDependency; 670 | target = 0894270E20B1BA0600A4B6FF /* KeyedCodable-macOS */; 671 | targetProxy = 0894271920B1BA0700A4B6FF /* PBXContainerItemProxy */; 672 | }; 673 | /* End PBXTargetDependency section */ 674 | 675 | /* Begin XCBuildConfiguration section */ 676 | 0889DB6620699EA8008A0A13 /* Debug */ = { 677 | isa = XCBuildConfiguration; 678 | buildSettings = { 679 | ALWAYS_SEARCH_USER_PATHS = NO; 680 | CLANG_ANALYZER_NONNULL = YES; 681 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 682 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 683 | CLANG_CXX_LIBRARY = "libc++"; 684 | CLANG_ENABLE_MODULES = YES; 685 | CLANG_ENABLE_OBJC_ARC = YES; 686 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 687 | CLANG_WARN_BOOL_CONVERSION = YES; 688 | CLANG_WARN_COMMA = YES; 689 | CLANG_WARN_CONSTANT_CONVERSION = YES; 690 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 691 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 692 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 693 | CLANG_WARN_EMPTY_BODY = YES; 694 | CLANG_WARN_ENUM_CONVERSION = YES; 695 | CLANG_WARN_INFINITE_RECURSION = YES; 696 | CLANG_WARN_INT_CONVERSION = YES; 697 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 698 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 699 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 700 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 701 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 702 | CLANG_WARN_STRICT_PROTOTYPES = YES; 703 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 704 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 705 | CLANG_WARN_UNREACHABLE_CODE = YES; 706 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 707 | CODE_SIGN_IDENTITY = "iPhone Developer"; 708 | COPY_PHASE_STRIP = NO; 709 | CURRENT_PROJECT_VERSION = 1; 710 | DEBUG_INFORMATION_FORMAT = dwarf; 711 | ENABLE_STRICT_OBJC_MSGSEND = YES; 712 | ENABLE_TESTABILITY = YES; 713 | GCC_C_LANGUAGE_STANDARD = gnu11; 714 | GCC_DYNAMIC_NO_PIC = NO; 715 | GCC_NO_COMMON_BLOCKS = YES; 716 | GCC_OPTIMIZATION_LEVEL = 0; 717 | GCC_PREPROCESSOR_DEFINITIONS = ( 718 | "DEBUG=1", 719 | "$(inherited)", 720 | ); 721 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 722 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 723 | GCC_WARN_UNDECLARED_SELECTOR = YES; 724 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 725 | GCC_WARN_UNUSED_FUNCTION = YES; 726 | GCC_WARN_UNUSED_VARIABLE = YES; 727 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 728 | MTL_ENABLE_DEBUG_INFO = YES; 729 | ONLY_ACTIVE_ARCH = YES; 730 | SDKROOT = iphoneos; 731 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 732 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 733 | VERSIONING_SYSTEM = "apple-generic"; 734 | VERSION_INFO_PREFIX = ""; 735 | }; 736 | name = Debug; 737 | }; 738 | 0889DB6720699EA8008A0A13 /* Release */ = { 739 | isa = XCBuildConfiguration; 740 | buildSettings = { 741 | ALWAYS_SEARCH_USER_PATHS = NO; 742 | CLANG_ANALYZER_NONNULL = YES; 743 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 744 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 745 | CLANG_CXX_LIBRARY = "libc++"; 746 | CLANG_ENABLE_MODULES = YES; 747 | CLANG_ENABLE_OBJC_ARC = YES; 748 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 749 | CLANG_WARN_BOOL_CONVERSION = YES; 750 | CLANG_WARN_COMMA = YES; 751 | CLANG_WARN_CONSTANT_CONVERSION = YES; 752 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 753 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 754 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 755 | CLANG_WARN_EMPTY_BODY = YES; 756 | CLANG_WARN_ENUM_CONVERSION = YES; 757 | CLANG_WARN_INFINITE_RECURSION = YES; 758 | CLANG_WARN_INT_CONVERSION = YES; 759 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 760 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 761 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 762 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 763 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 764 | CLANG_WARN_STRICT_PROTOTYPES = YES; 765 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 766 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 767 | CLANG_WARN_UNREACHABLE_CODE = YES; 768 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 769 | CODE_SIGN_IDENTITY = "iPhone Developer"; 770 | COPY_PHASE_STRIP = NO; 771 | CURRENT_PROJECT_VERSION = 1; 772 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 773 | ENABLE_NS_ASSERTIONS = NO; 774 | ENABLE_STRICT_OBJC_MSGSEND = YES; 775 | GCC_C_LANGUAGE_STANDARD = gnu11; 776 | GCC_NO_COMMON_BLOCKS = YES; 777 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 778 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 779 | GCC_WARN_UNDECLARED_SELECTOR = YES; 780 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 781 | GCC_WARN_UNUSED_FUNCTION = YES; 782 | GCC_WARN_UNUSED_VARIABLE = YES; 783 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 784 | MTL_ENABLE_DEBUG_INFO = NO; 785 | SDKROOT = iphoneos; 786 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 787 | VALIDATE_PRODUCT = YES; 788 | VERSIONING_SYSTEM = "apple-generic"; 789 | VERSION_INFO_PREFIX = ""; 790 | }; 791 | name = Release; 792 | }; 793 | 0889DB6920699EA8008A0A13 /* Debug */ = { 794 | isa = XCBuildConfiguration; 795 | buildSettings = { 796 | CLANG_ENABLE_MODULES = YES; 797 | CODE_SIGN_IDENTITY = ""; 798 | CODE_SIGN_STYLE = Automatic; 799 | DEFINES_MODULE = YES; 800 | DYLIB_COMPATIBILITY_VERSION = 1; 801 | DYLIB_CURRENT_VERSION = 1; 802 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 803 | INFOPLIST_FILE = KeyedCodable/Info.plist; 804 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 805 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 806 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 807 | PRODUCT_MODULE_NAME = KeyedCodable; 808 | PRODUCT_NAME = KeyedCodable; 809 | SKIP_INSTALL = YES; 810 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 811 | SWIFT_VERSION = 5.0; 812 | TARGETED_DEVICE_FAMILY = "1,2"; 813 | }; 814 | name = Debug; 815 | }; 816 | 0889DB6A20699EA8008A0A13 /* Release */ = { 817 | isa = XCBuildConfiguration; 818 | buildSettings = { 819 | CLANG_ENABLE_MODULES = YES; 820 | CODE_SIGN_IDENTITY = ""; 821 | CODE_SIGN_STYLE = Automatic; 822 | DEFINES_MODULE = YES; 823 | DYLIB_COMPATIBILITY_VERSION = 1; 824 | DYLIB_CURRENT_VERSION = 1; 825 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 826 | INFOPLIST_FILE = KeyedCodable/Info.plist; 827 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 828 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 829 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 830 | PRODUCT_MODULE_NAME = KeyedCodable; 831 | PRODUCT_NAME = KeyedCodable; 832 | SKIP_INSTALL = YES; 833 | SWIFT_VERSION = 5.0; 834 | TARGETED_DEVICE_FAMILY = "1,2"; 835 | }; 836 | name = Release; 837 | }; 838 | 0889DB6C20699EA8008A0A13 /* Debug */ = { 839 | isa = XCBuildConfiguration; 840 | buildSettings = { 841 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 842 | CODE_SIGN_STYLE = Automatic; 843 | INFOPLIST_FILE = KeyedCodableTests/Info.plist; 844 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 845 | PRODUCT_BUNDLE_IDENTIFIER = com.dgrzeszczak.KeyedCodableTests; 846 | PRODUCT_NAME = "$(TARGET_NAME)"; 847 | SWIFT_VERSION = 5.0; 848 | TARGETED_DEVICE_FAMILY = "1,2"; 849 | }; 850 | name = Debug; 851 | }; 852 | 0889DB6D20699EA8008A0A13 /* Release */ = { 853 | isa = XCBuildConfiguration; 854 | buildSettings = { 855 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 856 | CODE_SIGN_STYLE = Automatic; 857 | INFOPLIST_FILE = KeyedCodableTests/Info.plist; 858 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 859 | PRODUCT_BUNDLE_IDENTIFIER = com.dgrzeszczak.KeyedCodableTests; 860 | PRODUCT_NAME = "$(TARGET_NAME)"; 861 | SWIFT_VERSION = 5.0; 862 | TARGETED_DEVICE_FAMILY = "1,2"; 863 | }; 864 | name = Release; 865 | }; 866 | 089426EC20B1B87900A4B6FF /* Debug */ = { 867 | isa = XCBuildConfiguration; 868 | buildSettings = { 869 | APPLICATION_EXTENSION_API_ONLY = YES; 870 | CLANG_ENABLE_OBJC_WEAK = YES; 871 | CODE_SIGN_IDENTITY = ""; 872 | CODE_SIGN_STYLE = Automatic; 873 | DEFINES_MODULE = YES; 874 | DYLIB_COMPATIBILITY_VERSION = 1; 875 | DYLIB_CURRENT_VERSION = 1; 876 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 877 | INFOPLIST_FILE = KeyedCodable/Info.plist; 878 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 879 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 880 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 881 | PRODUCT_MODULE_NAME = KeyedCodable; 882 | PRODUCT_NAME = KeyedCodable; 883 | SDKROOT = watchos; 884 | SKIP_INSTALL = YES; 885 | SWIFT_VERSION = 4.0; 886 | TARGETED_DEVICE_FAMILY = 4; 887 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 888 | }; 889 | name = Debug; 890 | }; 891 | 089426ED20B1B87900A4B6FF /* Release */ = { 892 | isa = XCBuildConfiguration; 893 | buildSettings = { 894 | APPLICATION_EXTENSION_API_ONLY = YES; 895 | CLANG_ENABLE_OBJC_WEAK = YES; 896 | CODE_SIGN_IDENTITY = ""; 897 | CODE_SIGN_STYLE = Automatic; 898 | DEFINES_MODULE = YES; 899 | DYLIB_COMPATIBILITY_VERSION = 1; 900 | DYLIB_CURRENT_VERSION = 1; 901 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 902 | INFOPLIST_FILE = KeyedCodable/Info.plist; 903 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 904 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 905 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 906 | PRODUCT_MODULE_NAME = KeyedCodable; 907 | PRODUCT_NAME = KeyedCodable; 908 | SDKROOT = watchos; 909 | SKIP_INSTALL = YES; 910 | SWIFT_VERSION = 4.0; 911 | TARGETED_DEVICE_FAMILY = 4; 912 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 913 | }; 914 | name = Release; 915 | }; 916 | 0894270520B1B9E300A4B6FF /* Debug */ = { 917 | isa = XCBuildConfiguration; 918 | buildSettings = { 919 | CLANG_ENABLE_OBJC_WEAK = YES; 920 | CODE_SIGN_IDENTITY = ""; 921 | CODE_SIGN_STYLE = Automatic; 922 | DEFINES_MODULE = YES; 923 | DYLIB_COMPATIBILITY_VERSION = 1; 924 | DYLIB_CURRENT_VERSION = 1; 925 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 926 | INFOPLIST_FILE = KeyedCodable/Info.plist; 927 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 928 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 929 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 930 | PRODUCT_MODULE_NAME = KeyedCodable; 931 | PRODUCT_NAME = KeyedCodable; 932 | SDKROOT = appletvos; 933 | SKIP_INSTALL = YES; 934 | SWIFT_VERSION = 4.0; 935 | TARGETED_DEVICE_FAMILY = 3; 936 | TVOS_DEPLOYMENT_TARGET = 9.0; 937 | }; 938 | name = Debug; 939 | }; 940 | 0894270620B1B9E300A4B6FF /* Release */ = { 941 | isa = XCBuildConfiguration; 942 | buildSettings = { 943 | CLANG_ENABLE_OBJC_WEAK = YES; 944 | CODE_SIGN_IDENTITY = ""; 945 | CODE_SIGN_STYLE = Automatic; 946 | DEFINES_MODULE = YES; 947 | DYLIB_COMPATIBILITY_VERSION = 1; 948 | DYLIB_CURRENT_VERSION = 1; 949 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 950 | INFOPLIST_FILE = KeyedCodable/Info.plist; 951 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 952 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 953 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 954 | PRODUCT_MODULE_NAME = KeyedCodable; 955 | PRODUCT_NAME = KeyedCodable; 956 | SDKROOT = appletvos; 957 | SKIP_INSTALL = YES; 958 | SWIFT_VERSION = 4.0; 959 | TARGETED_DEVICE_FAMILY = 3; 960 | TVOS_DEPLOYMENT_TARGET = 9.0; 961 | }; 962 | name = Release; 963 | }; 964 | 0894270820B1B9E300A4B6FF /* Debug */ = { 965 | isa = XCBuildConfiguration; 966 | buildSettings = { 967 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 968 | CLANG_ENABLE_OBJC_WEAK = YES; 969 | CODE_SIGN_STYLE = Automatic; 970 | INFOPLIST_FILE = KeyedCodableTests/Info.plist; 971 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 972 | PRODUCT_BUNDLE_IDENTIFIER = "com.db.KeyedCodable-tvOSTests"; 973 | PRODUCT_NAME = "$(TARGET_NAME)"; 974 | SDKROOT = appletvos; 975 | SWIFT_VERSION = 4.0; 976 | TARGETED_DEVICE_FAMILY = 3; 977 | TVOS_DEPLOYMENT_TARGET = 11.3; 978 | }; 979 | name = Debug; 980 | }; 981 | 0894270920B1B9E300A4B6FF /* Release */ = { 982 | isa = XCBuildConfiguration; 983 | buildSettings = { 984 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 985 | CLANG_ENABLE_OBJC_WEAK = YES; 986 | CODE_SIGN_STYLE = Automatic; 987 | INFOPLIST_FILE = KeyedCodableTests/Info.plist; 988 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 989 | PRODUCT_BUNDLE_IDENTIFIER = "com.db.KeyedCodable-tvOSTests"; 990 | PRODUCT_NAME = "$(TARGET_NAME)"; 991 | SDKROOT = appletvos; 992 | SWIFT_VERSION = 4.0; 993 | TARGETED_DEVICE_FAMILY = 3; 994 | TVOS_DEPLOYMENT_TARGET = 11.3; 995 | }; 996 | name = Release; 997 | }; 998 | 0894272120B1BA0700A4B6FF /* Debug */ = { 999 | isa = XCBuildConfiguration; 1000 | buildSettings = { 1001 | CLANG_ENABLE_OBJC_WEAK = YES; 1002 | CODE_SIGN_IDENTITY = "-"; 1003 | CODE_SIGN_STYLE = Automatic; 1004 | COMBINE_HIDPI_IMAGES = YES; 1005 | DEFINES_MODULE = YES; 1006 | DYLIB_COMPATIBILITY_VERSION = 1; 1007 | DYLIB_CURRENT_VERSION = 1; 1008 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1009 | FRAMEWORK_VERSION = A; 1010 | INFOPLIST_FILE = KeyedCodable/Info.plist; 1011 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1012 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 1013 | MACOSX_DEPLOYMENT_TARGET = 10.9; 1014 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 1015 | PRODUCT_MODULE_NAME = KeyedCodable; 1016 | PRODUCT_NAME = KeyedCodable; 1017 | SDKROOT = macosx; 1018 | SKIP_INSTALL = YES; 1019 | SWIFT_VERSION = 4.0; 1020 | }; 1021 | name = Debug; 1022 | }; 1023 | 0894272220B1BA0700A4B6FF /* Release */ = { 1024 | isa = XCBuildConfiguration; 1025 | buildSettings = { 1026 | CLANG_ENABLE_OBJC_WEAK = YES; 1027 | CODE_SIGN_IDENTITY = "-"; 1028 | CODE_SIGN_STYLE = Automatic; 1029 | COMBINE_HIDPI_IMAGES = YES; 1030 | DEFINES_MODULE = YES; 1031 | DYLIB_COMPATIBILITY_VERSION = 1; 1032 | DYLIB_CURRENT_VERSION = 1; 1033 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1034 | FRAMEWORK_VERSION = A; 1035 | INFOPLIST_FILE = KeyedCodable/Info.plist; 1036 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1037 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 1038 | MACOSX_DEPLOYMENT_TARGET = 10.9; 1039 | PRODUCT_BUNDLE_IDENTIFIER = com.dg.KeyedCodable; 1040 | PRODUCT_MODULE_NAME = KeyedCodable; 1041 | PRODUCT_NAME = KeyedCodable; 1042 | SDKROOT = macosx; 1043 | SKIP_INSTALL = YES; 1044 | SWIFT_VERSION = 4.0; 1045 | }; 1046 | name = Release; 1047 | }; 1048 | 0894272420B1BA0700A4B6FF /* Debug */ = { 1049 | isa = XCBuildConfiguration; 1050 | buildSettings = { 1051 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1052 | CLANG_ENABLE_OBJC_WEAK = YES; 1053 | CODE_SIGN_IDENTITY = "-"; 1054 | CODE_SIGN_STYLE = Automatic; 1055 | COMBINE_HIDPI_IMAGES = YES; 1056 | INFOPLIST_FILE = KeyedCodableTests/Info.plist; 1057 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1058 | MACOSX_DEPLOYMENT_TARGET = 10.13; 1059 | PRODUCT_BUNDLE_IDENTIFIER = "com.db.KeyedCodable-macOSTests"; 1060 | PRODUCT_NAME = "$(TARGET_NAME)"; 1061 | SDKROOT = macosx; 1062 | SWIFT_VERSION = 4.0; 1063 | }; 1064 | name = Debug; 1065 | }; 1066 | 0894272520B1BA0700A4B6FF /* Release */ = { 1067 | isa = XCBuildConfiguration; 1068 | buildSettings = { 1069 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1070 | CLANG_ENABLE_OBJC_WEAK = YES; 1071 | CODE_SIGN_IDENTITY = "-"; 1072 | CODE_SIGN_STYLE = Automatic; 1073 | COMBINE_HIDPI_IMAGES = YES; 1074 | INFOPLIST_FILE = KeyedCodableTests/Info.plist; 1075 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1076 | MACOSX_DEPLOYMENT_TARGET = 10.13; 1077 | PRODUCT_BUNDLE_IDENTIFIER = "com.db.KeyedCodable-macOSTests"; 1078 | PRODUCT_NAME = "$(TARGET_NAME)"; 1079 | SDKROOT = macosx; 1080 | SWIFT_VERSION = 4.0; 1081 | }; 1082 | name = Release; 1083 | }; 1084 | /* End XCBuildConfiguration section */ 1085 | 1086 | /* Begin XCConfigurationList section */ 1087 | 0889DB4E20699EA8008A0A13 /* Build configuration list for PBXProject "KeyedCodable" */ = { 1088 | isa = XCConfigurationList; 1089 | buildConfigurations = ( 1090 | 0889DB6620699EA8008A0A13 /* Debug */, 1091 | 0889DB6720699EA8008A0A13 /* Release */, 1092 | ); 1093 | defaultConfigurationIsVisible = 0; 1094 | defaultConfigurationName = Release; 1095 | }; 1096 | 0889DB6820699EA8008A0A13 /* Build configuration list for PBXNativeTarget "KeyedCodable-iOS" */ = { 1097 | isa = XCConfigurationList; 1098 | buildConfigurations = ( 1099 | 0889DB6920699EA8008A0A13 /* Debug */, 1100 | 0889DB6A20699EA8008A0A13 /* Release */, 1101 | ); 1102 | defaultConfigurationIsVisible = 0; 1103 | defaultConfigurationName = Release; 1104 | }; 1105 | 0889DB6B20699EA8008A0A13 /* Build configuration list for PBXNativeTarget "KeyedCodableTests-iOS" */ = { 1106 | isa = XCConfigurationList; 1107 | buildConfigurations = ( 1108 | 0889DB6C20699EA8008A0A13 /* Debug */, 1109 | 0889DB6D20699EA8008A0A13 /* Release */, 1110 | ); 1111 | defaultConfigurationIsVisible = 0; 1112 | defaultConfigurationName = Release; 1113 | }; 1114 | 089426EB20B1B87900A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-watchOS" */ = { 1115 | isa = XCConfigurationList; 1116 | buildConfigurations = ( 1117 | 089426EC20B1B87900A4B6FF /* Debug */, 1118 | 089426ED20B1B87900A4B6FF /* Release */, 1119 | ); 1120 | defaultConfigurationIsVisible = 0; 1121 | defaultConfigurationName = Release; 1122 | }; 1123 | 0894270420B1B9E300A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-tvOS" */ = { 1124 | isa = XCConfigurationList; 1125 | buildConfigurations = ( 1126 | 0894270520B1B9E300A4B6FF /* Debug */, 1127 | 0894270620B1B9E300A4B6FF /* Release */, 1128 | ); 1129 | defaultConfigurationIsVisible = 0; 1130 | defaultConfigurationName = Release; 1131 | }; 1132 | 0894270720B1B9E300A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-tvOSTests" */ = { 1133 | isa = XCConfigurationList; 1134 | buildConfigurations = ( 1135 | 0894270820B1B9E300A4B6FF /* Debug */, 1136 | 0894270920B1B9E300A4B6FF /* Release */, 1137 | ); 1138 | defaultConfigurationIsVisible = 0; 1139 | defaultConfigurationName = Release; 1140 | }; 1141 | 0894272020B1BA0700A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-macOS" */ = { 1142 | isa = XCConfigurationList; 1143 | buildConfigurations = ( 1144 | 0894272120B1BA0700A4B6FF /* Debug */, 1145 | 0894272220B1BA0700A4B6FF /* Release */, 1146 | ); 1147 | defaultConfigurationIsVisible = 0; 1148 | defaultConfigurationName = Release; 1149 | }; 1150 | 0894272320B1BA0700A4B6FF /* Build configuration list for PBXNativeTarget "KeyedCodable-macOSTests" */ = { 1151 | isa = XCConfigurationList; 1152 | buildConfigurations = ( 1153 | 0894272420B1BA0700A4B6FF /* Debug */, 1154 | 0894272520B1BA0700A4B6FF /* Release */, 1155 | ); 1156 | defaultConfigurationIsVisible = 0; 1157 | defaultConfigurationName = Release; 1158 | }; 1159 | /* End XCConfigurationList section */ 1160 | }; 1161 | rootObject = 0889DB4B20699EA8008A0A13 /* Project object */; 1162 | } 1163 | -------------------------------------------------------------------------------- /KeyedCodable.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KeyedCodable.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KeyedCodable.xcodeproj/xcshareddata/xcschemes/KeyedCodable-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /KeyedCodable.xcodeproj/xcshareddata/xcschemes/KeyedCodable-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /KeyedCodable.xcodeproj/xcshareddata/xcschemes/KeyedCodable-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /KeyedCodable.xcodeproj/xcshareddata/xcschemes/KeyedCodable-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 | -------------------------------------------------------------------------------- /KeyedCodable/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 2.5.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /KeyedCodable/KeyedCodable.h: -------------------------------------------------------------------------------- 1 | // 2 | // KeyedCodable.h 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 26/03/2018. 6 | // Copyright © 2018 Dariusz Grzeszczak. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for KeyedCodable. 12 | FOUNDATION_EXPORT double KeyedCodableVersionNumber; 13 | 14 | //! Project version string for KeyedCodable. 15 | FOUNDATION_EXPORT const unsigned char KeyedCodableVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/AnyKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnyKey.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 26/03/2018. 6 | // Copyright © 2018 Dariusz Grzeszczak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct AnyKey: CodingKey, Hashable, AnyKeyedKey { 12 | 13 | public let stringValue: String 14 | public let intValue: Int? 15 | 16 | public let options: KeyOptions? 17 | 18 | public init(stringValue: String, options: KeyOptions) { 19 | self.stringValue = stringValue 20 | intValue = nil 21 | self.options = options 22 | } 23 | 24 | public init(intValue: Int, options: KeyOptions) { 25 | stringValue = String(intValue) 26 | self.intValue = intValue 27 | self.options = options 28 | } 29 | 30 | public init(stringValue: String) { 31 | self.stringValue = stringValue 32 | intValue = nil 33 | options = nil 34 | } 35 | 36 | public init(intValue: Int) { 37 | stringValue = String(intValue) 38 | self.intValue = intValue 39 | options = nil 40 | } 41 | 42 | public init(key: CodingKey, options: KeyOptions? = nil) { 43 | if let intValue = key.intValue { self.init(intValue: intValue) } 44 | else { self.init(stringValue: key.stringValue )} 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 26/03/2018. 6 | // Copyright © 2018 Dariusz Grzeszczak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension AnyKey { 12 | static var superKey: AnyKey { return AnyKey(stringValue: "super") } 13 | } 14 | 15 | extension CodingKey { 16 | 17 | init?(_ key: AnyKey) { 18 | if let intValue = key.intValue, let zelf = Self(intValue: intValue) { 19 | self = zelf 20 | } else if let zelf = Self(stringValue: key.stringValue) { 21 | self = zelf 22 | } else { 23 | return nil 24 | } 25 | } 26 | 27 | var isFlat: Bool { 28 | guard let key = self as? AnyKeyedKey else { return false } 29 | return (key.options?.flat ?? KeyedConfig.default.keyOptions.flat).isFlat(key: key) 30 | } 31 | 32 | var isFirstFlat: Bool { 33 | guard let key = self as? AnyKeyedKey else { return false } 34 | let keyed = key.keyed 35 | guard keyed.count > 1 else { return false } 36 | return (key.options?.flat ?? KeyedConfig.default.keyOptions.flat).isFlat(key: keyed[0]) 37 | } 38 | 39 | var keyed: [AnyKey] { 40 | guard let key = self as? AnyKeyedKey else { return [AnyKey(key: self)] } 41 | guard case .character(let character) = key.options?.delimiter ?? KeyedConfig.default.keyOptions.delimiter else { return [AnyKey(key: key)] } 42 | 43 | return stringValue //TODO: indexes ?? 44 | .components(separatedBy: String(character)) 45 | .compactMap(AnyKey.init) 46 | } 47 | } 48 | 49 | protocol _Array { 50 | static func optionalDecode(unkeyedContainer: UnkeyedDecodingContainer) -> T 51 | } 52 | 53 | extension Array: _Array where Element: Decodable { 54 | static func optionalDecode(unkeyedContainer: UnkeyedDecodingContainer) -> T { 55 | var unkeyedContainer = unkeyedContainer 56 | var newObject = [Element]() 57 | while !unkeyedContainer.isAtEnd { 58 | if let value = try? unkeyedContainer.decode(Element.self) { 59 | newObject.append(value) 60 | } 61 | // swift 4 ? 62 | // else { 63 | // _ = try? unkeyedContainer.decode(EmptyCodable.self) 64 | // } 65 | } 66 | return newObject as! T 67 | } 68 | } 69 | 70 | extension Data { 71 | static func from(string: String, encoding: String.Encoding = .utf8) throws -> Data { 72 | guard let data = string.data(using: encoding) else { throw KeyedCodableError.stringParseFailed } 73 | return data 74 | } 75 | } 76 | 77 | extension String { 78 | static func from(data: Data, encoding: String.Encoding = .utf8) throws -> String { 79 | guard let string = String(data: data, encoding: encoding) else { throw KeyedCodableError.stringParseFailed } 80 | return string 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/KeyedConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyedConfig.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 26/03/2018. 6 | // Copyright © 2018 Dariusz Grzeszczak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct KeyedConfig { 12 | 13 | public static var `default` = KeyedConfig() 14 | 15 | public var keyOptions: KeyOptions 16 | public var defaultJSONDecoder: () -> KeyedJSONDecoder 17 | public var defaultJSONEncoder: () -> KeyedJSONEncoder 18 | 19 | public init(keyOptions: KeyOptions = KeyOptions(delimiter: .character("."), flat: .emptyOrWhitespace), 20 | keyedJSONDecoder: @escaping () -> KeyedJSONDecoder = KeyedJSONDecoder.init, 21 | keyedJSONEncoder: @escaping () -> KeyedJSONEncoder = KeyedJSONEncoder.init) { 22 | 23 | self.keyOptions = keyOptions 24 | defaultJSONDecoder = keyedJSONDecoder 25 | defaultJSONEncoder = keyedJSONEncoder 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/KeyedJSONDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyedJSONDecoder.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 26/03/2018. 6 | // Copyright © 2018 Dariusz Grzeszczak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class KeyedJSONDecoder: JSONDecoder { 12 | 13 | open override func decode(_ type: T.Type, from data: Data) throws -> T { 14 | 15 | if T.self == String.self { 16 | return try String.from(data: data) as! T 17 | } else if T.self == Int.self { 18 | guard let int = try Int(String.from(data: data)) else { throw KeyedCodableError.stringParseFailed } 19 | return int as! T 20 | } else if let type = T.self as? LosslessStringConvertible.Type { 21 | guard let lossless = try type.init(String.from(data: data)) else { throw KeyedCodableError.stringParseFailed } 22 | return lossless as! T 23 | } else { 24 | return try super.decode(Keyed.self, from: data).value 25 | } 26 | } 27 | } 28 | 29 | extension Decodable { 30 | 31 | public static var keyed: Keyed.Type { return Keyed.self } 32 | 33 | @available(*, deprecated, message: "Use DecodableType.keyed.fromJSON() instead. Replace all occurences of '(fromJSON: ' with '.keyed.fromJSON('.") 34 | public init(fromJSON data: Data, decoder: KeyedJSONDecoder = KeyedConfig.default.defaultJSONDecoder()) throws { 35 | 36 | self = try Self.keyed.fromJSON(data, decoder: decoder) 37 | } 38 | 39 | @available(*, deprecated, message: "Use DecodableType.keyed.fromJSON() instead. Replace all occurences of '(fromJSON: ' with '.keyed.fromJSON('.") 40 | public init(fromJSON string: String, encoding: String.Encoding = .utf8, decoder: KeyedJSONDecoder = KeyedConfig.default.defaultJSONDecoder()) throws { 41 | 42 | self = try Self.keyed.fromJSON(string, encoding: encoding, decoder: decoder) 43 | } 44 | } 45 | 46 | extension Keyed where Base: Decodable { 47 | 48 | public static func fromJSON(_ data: Data, decoder: KeyedJSONDecoder = KeyedConfig.default.defaultJSONDecoder()) throws -> Base { 49 | 50 | return try decoder.decode(Base.self, from: data) 51 | } 52 | 53 | public static func fromJSON(_ string: String, encoding: String.Encoding = .utf8, decoder: KeyedJSONDecoder = KeyedConfig.default.defaultJSONDecoder()) throws -> Base { 54 | return try fromJSON(Data.from(string: string, encoding: encoding), decoder: decoder) 55 | } 56 | } 57 | 58 | extension Keyed: Decodable where Base: Decodable { 59 | public init(from decoder: Decoder) throws { 60 | if let keyedDecoder = decoder as? KeyedDecoder { 61 | value = try keyedDecoder.singleValueContainer().decode(Base.self) 62 | } else { 63 | value = try Base(from: KeyedDecoder(decoder: decoder, codingPath: decoder.codingPath)) 64 | } 65 | } 66 | } 67 | 68 | // MARK: internal 69 | 70 | struct KeyedDecoder: Decoder { 71 | let decoder: Decoder 72 | let codingPath: [CodingKey] 73 | 74 | var userInfo: [CodingUserInfoKey : Any] { return decoder.userInfo } 75 | 76 | func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { 77 | return try KeyedDecodingContainer(KeyedKeyedDecodingContainer(keyedDecoder: self)) 78 | } 79 | 80 | func unkeyedContainer() throws -> UnkeyedDecodingContainer { 81 | return try KeyedUnkeyedDecodingContainer(keyedDecoder: self) 82 | } 83 | 84 | func singleValueContainer() throws -> SingleValueDecodingContainer { 85 | return try KeyedSingleValueDecodingContainer(keyedDecoder: self) 86 | } 87 | } 88 | 89 | struct KeyedSingleValueDecodingContainer: SingleValueDecodingContainer { 90 | 91 | private let keyedDecoder: KeyedDecoder 92 | private let container: SingleValueDecodingContainer 93 | 94 | var codingPath: [CodingKey] { return keyedDecoder.codingPath } 95 | 96 | init(keyedDecoder: KeyedDecoder) throws { 97 | self.keyedDecoder = keyedDecoder 98 | container = try keyedDecoder.decoder.singleValueContainer() 99 | } 100 | 101 | func decodeNil() -> Bool { 102 | return container.decodeNil() 103 | } 104 | 105 | func decode(_ type: Bool.Type) throws -> Bool { 106 | return try container.decode(type) 107 | } 108 | 109 | func decode(_ type: String.Type) throws -> String { 110 | return try container.decode(type) 111 | } 112 | 113 | func decode(_ type: Double.Type) throws -> Double { 114 | return try container.decode(type) 115 | } 116 | 117 | func decode(_ type: Float.Type) throws -> Float { 118 | return try container.decode(type) 119 | } 120 | 121 | func decode(_ type: Int.Type) throws -> Int { 122 | return try container.decode(type) 123 | } 124 | 125 | func decode(_ type: Int8.Type) throws -> Int8 { 126 | return try container.decode(type) 127 | } 128 | 129 | func decode(_ type: Int16.Type) throws -> Int16 { 130 | return try container.decode(type) 131 | } 132 | 133 | func decode(_ type: Int32.Type) throws -> Int32 { 134 | return try container.decode(type) 135 | } 136 | 137 | func decode(_ type: Int64.Type) throws -> Int64 { 138 | return try container.decode(type) 139 | } 140 | 141 | func decode(_ type: UInt.Type) throws -> UInt { 142 | return try container.decode(type) 143 | } 144 | 145 | func decode(_ type: UInt8.Type) throws -> UInt8 { 146 | return try container.decode(type) 147 | } 148 | 149 | func decode(_ type: UInt16.Type) throws -> UInt16 { 150 | return try container.decode(type) 151 | } 152 | 153 | func decode(_ type: UInt32.Type) throws -> UInt32 { 154 | return try container.decode(type) 155 | } 156 | 157 | func decode(_ type: UInt64.Type) throws -> UInt64 { 158 | return try container.decode(type) 159 | } 160 | 161 | func decode(_ type: T.Type) throws -> T where T : Decodable { 162 | return try T(from: keyedDecoder) 163 | } 164 | } 165 | 166 | struct KeyedUnkeyedDecodingContainer: UnkeyedDecodingContainer { 167 | 168 | private let keyedDecoder: KeyedDecoder 169 | private var container: UnkeyedDecodingContainer 170 | 171 | init(keyedDecoder: KeyedDecoder) throws { 172 | self.keyedDecoder = keyedDecoder 173 | container = try keyedDecoder.decoder.unkeyedContainer() 174 | } 175 | 176 | var codingPath: [CodingKey] { return keyedDecoder.codingPath } 177 | 178 | var count: Int? { return container.count } 179 | 180 | var isAtEnd: Bool { return container.isAtEnd } 181 | 182 | var currentIndex: Int { return container.currentIndex } 183 | 184 | mutating func decodeNil() throws -> Bool { 185 | return try container.decodeNil() 186 | } 187 | 188 | mutating func decode(_ type: Bool.Type) throws -> Bool { 189 | return try container.decode(type) 190 | } 191 | 192 | mutating func decode(_ type: String.Type) throws -> String { 193 | return try container.decode(type) 194 | } 195 | 196 | mutating func decode(_ type: Double.Type) throws -> Double { 197 | return try container.decode(type) 198 | } 199 | 200 | mutating func decode(_ type: Float.Type) throws -> Float { 201 | return try container.decode(type) 202 | } 203 | 204 | mutating func decode(_ type: Int.Type) throws -> Int { 205 | return try container.decode(type) 206 | } 207 | 208 | mutating func decode(_ type: Int8.Type) throws -> Int8 { 209 | return try container.decode(type) 210 | } 211 | 212 | mutating func decode(_ type: Int16.Type) throws -> Int16 { 213 | return try container.decode(type) 214 | } 215 | 216 | mutating func decode(_ type: Int32.Type) throws -> Int32 { 217 | return try container.decode(type) 218 | } 219 | 220 | mutating func decode(_ type: Int64.Type) throws -> Int64 { 221 | return try container.decode(type) 222 | } 223 | 224 | mutating func decode(_ type: UInt.Type) throws -> UInt { 225 | return try container.decode(type) 226 | } 227 | 228 | mutating func decode(_ type: UInt8.Type) throws -> UInt8 { 229 | return try container.decode(type) 230 | } 231 | 232 | mutating func decode(_ type: UInt16.Type) throws -> UInt16 { 233 | return try container.decode(type) 234 | } 235 | 236 | mutating func decode(_ type: UInt32.Type) throws -> UInt32 { 237 | return try container.decode(type) 238 | } 239 | 240 | mutating func decode(_ type: UInt64.Type) throws -> UInt64 { 241 | return try container.decode(type) 242 | } 243 | 244 | mutating func decode(_ type: T.Type) throws -> T where T : Decodable { 245 | return try T(from: superDecoder()) 246 | } 247 | 248 | mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 249 | return try KeyedDecodingContainer(KeyedKeyedDecodingContainer(keyedDecoder: superDecoder() as! KeyedDecoder)) 250 | } 251 | 252 | mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { 253 | return try KeyedUnkeyedDecodingContainer(keyedDecoder: superDecoder() as! KeyedDecoder) 254 | } 255 | 256 | mutating func superDecoder() throws -> Decoder { 257 | let decoder = try container.superDecoder() 258 | let lastPath = decoder.codingPath[decoder.codingPath.count - 1] 259 | return KeyedDecoder(decoder: decoder, codingPath: codingPath + [lastPath]) 260 | } 261 | } 262 | 263 | final class KeyedKeyedDecodingContainer: KeyedDecodingContainerProtocol { 264 | 265 | private let keyedDecoder: KeyedDecoder 266 | private var container: KeyedDecodingContainer 267 | 268 | init(keyedDecoder: KeyedDecoder) throws { 269 | self.keyedDecoder = keyedDecoder 270 | container = try keyedDecoder.decoder.container(keyedBy: AnyKey.self) 271 | } 272 | 273 | var codingPath: [CodingKey] { return keyedDecoder.codingPath } 274 | 275 | var allKeys: [K] { 276 | guard let type = K.self as? CaseIterableKey.Type else { return container.allKeys.compactMap(K.init) } 277 | return type.allKeys.compactMap { K(stringValue: $0.stringValue) }.filter(contains) 278 | } 279 | 280 | func contains(_ key: K) -> Bool { 281 | if key.isFlat { return true } 282 | guard let contanerWithKey = try? boxedContainer(for: key) else { return false } 283 | return contanerWithKey.0.contains(contanerWithKey.1) 284 | } 285 | 286 | func decodeNil(forKey key: K) throws -> Bool { 287 | if key.isFlat { return false } 288 | guard contains(key) else { return true } 289 | let contanerWithKey = try boxedContainer(for: key) 290 | return try contanerWithKey.0.decodeNil(forKey: contanerWithKey.1) 291 | } 292 | 293 | func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { 294 | let contanerWithKey = try boxedContainer(for: key) 295 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 296 | } 297 | 298 | func decode(_ type: String.Type, forKey key: K) throws -> String { 299 | let contanerWithKey = try boxedContainer(for: key) 300 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 301 | } 302 | 303 | func decode(_ type: Double.Type, forKey key: K) throws -> Double { 304 | let contanerWithKey = try boxedContainer(for: key) 305 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 306 | } 307 | 308 | func decode(_ type: Float.Type, forKey key: K) throws -> Float { 309 | let contanerWithKey = try boxedContainer(for: key) 310 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 311 | } 312 | 313 | func decode(_ type: Int.Type, forKey key: K) throws -> Int { 314 | let contanerWithKey = try boxedContainer(for: key) 315 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 316 | } 317 | 318 | func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { 319 | let contanerWithKey = try boxedContainer(for: key) 320 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 321 | } 322 | 323 | func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { 324 | let contanerWithKey = try boxedContainer(for: key) 325 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 326 | } 327 | 328 | func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { 329 | let contanerWithKey = try boxedContainer(for: key) 330 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 331 | } 332 | 333 | func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { 334 | let contanerWithKey = try boxedContainer(for: key) 335 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 336 | } 337 | 338 | func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { 339 | let contanerWithKey = try boxedContainer(for: key) 340 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 341 | } 342 | 343 | func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { 344 | let contanerWithKey = try boxedContainer(for: key) 345 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 346 | } 347 | 348 | func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { 349 | let contanerWithKey = try boxedContainer(for: key) 350 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 351 | } 352 | 353 | func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { 354 | let contanerWithKey = try boxedContainer(for: key) 355 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 356 | } 357 | 358 | func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { 359 | let contanerWithKey = try boxedContainer(for: key) 360 | return try contanerWithKey.0.decode(type, forKey: contanerWithKey.1) 361 | } 362 | 363 | func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { 364 | #if swift(>=5.1) 365 | if let type = type as? FlatType.Type { 366 | if type.isArray { 367 | return try T(from: superDecoder(forKey: key)) 368 | } else { 369 | return try T(from: keyedDecoder) 370 | } 371 | } else { 372 | return try decodeArray(type, forKey: key) ?? T(from: superDecoder(forKey: key)) 373 | } 374 | #else 375 | return try decodeArray(type, forKey: key) ?? T(from: superDecoder(forKey: key)) 376 | #endif 377 | } 378 | 379 | private func decodeArray(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable { 380 | if let type = type as? _Array.Type, key.isFirstFlat { 381 | return type.optionalDecode(unkeyedContainer: try nestedUnkeyedContainer(forKey: key)) 382 | } 383 | return nil 384 | } 385 | 386 | func decodeIfPresent(_ type: T.Type, forKey key: Key) throws -> T? where T : Decodable { 387 | if key.isFlat { 388 | return try? decode(type, forKey: key) 389 | } else if try decodeNil(forKey: key) { 390 | return .none 391 | } else { 392 | return try decode(type, forKey: key) 393 | } 394 | } 395 | 396 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 397 | let decoder = try superDecoder(forKey: key) 398 | return try KeyedDecodingContainer(KeyedKeyedDecodingContainer(keyedDecoder: decoder as! KeyedDecoder)) 399 | } 400 | 401 | func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { 402 | let decoder = try superDecoder(forKey: key) 403 | return try KeyedUnkeyedDecodingContainer(keyedDecoder: decoder as! KeyedDecoder) 404 | } 405 | 406 | func superDecoder() throws -> Decoder { 407 | let decoder = try container.superDecoder() 408 | return KeyedDecoder(decoder: decoder, codingPath: codingPath + [AnyKey.superKey]) 409 | } 410 | 411 | func superDecoder(forKey key: K) throws -> Decoder { 412 | if key.isFlat { 413 | return keyedDecoder 414 | } else if key.isFirstFlat { 415 | let contanerWithKey = try boxedContainer(for: Array(key.keyed.dropFirst())) 416 | let decoder = try contanerWithKey.0.superDecoder(forKey: contanerWithKey.1) 417 | return KeyedDecoder(decoder: decoder, codingPath: codingPath + [key]) 418 | } else { 419 | let contanerWithKey = try boxedContainer(for: key) 420 | let decoder = try contanerWithKey.0.superDecoder(forKey: contanerWithKey.1) 421 | return KeyedDecoder(decoder: decoder, codingPath: codingPath + [key]) 422 | } 423 | } 424 | 425 | private func boxedContainer(for key: K) throws -> (KeyedDecodingContainer, AnyKey) { 426 | guard let _ = K.self as? AnyKeyedKey.Type else { return (self.container, AnyKey(key: key)) } 427 | return try boxedContainer(for: key.keyed) 428 | } 429 | 430 | private func boxedContainer(for keyPath: [AnyKey]) throws -> (KeyedDecodingContainer, AnyKey) { 431 | var keys: [AnyKey] = keyPath 432 | 433 | let key = keys.removeLast() 434 | var container = self.container 435 | 436 | try keys.forEach { 437 | container = try container.nestedContainer(keyedBy: AnyKey.self, forKey: $0) 438 | } 439 | 440 | return (container, key) 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/KeyedJSONEncoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyedJSONEncoder.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 26/03/2018. 6 | // Copyright © 2018 Dariusz Grzeszczak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class KeyedJSONEncoder: JSONEncoder { 12 | 13 | open override func encode(_ value: T) throws -> Data where T : Encodable { 14 | if let string = value as? String { 15 | return try Data.from(string: string) 16 | } else if let int = value as? Int { 17 | return try Data.from(string: "\(int)") 18 | } else if let lossless = value as? LosslessStringConvertible { 19 | return try Data.from(string: lossless.description) 20 | } else { 21 | return try super.encode(Keyed(value)) 22 | } 23 | } 24 | } 25 | 26 | extension Encodable { 27 | 28 | public var keyed: Keyed { return Keyed(self) } 29 | 30 | @available(*, deprecated, message: "Use keyed.jsonData() instead") 31 | public func jsonData(encoder: KeyedJSONEncoder = KeyedConfig.default.defaultJSONEncoder()) throws -> Data { 32 | 33 | return try keyed.jsonData(encoder: encoder) 34 | } 35 | 36 | @available(*, deprecated, message: "Use keyed.jsonString() instead") 37 | public func jsonString(encoding: String.Encoding = .utf8, encoder: KeyedJSONEncoder = KeyedConfig.default.defaultJSONEncoder()) throws -> String { 38 | 39 | return try keyed.jsonString(encoding: encoding, encoder: encoder) 40 | } 41 | } 42 | 43 | extension Keyed where Base: Encodable { 44 | 45 | public func jsonData(encoder: KeyedJSONEncoder = KeyedConfig.default.defaultJSONEncoder()) throws -> Data { 46 | return try encoder.encode(value) 47 | } 48 | 49 | public func jsonString(encoding: String.Encoding = .utf8, encoder: KeyedJSONEncoder = KeyedConfig.default.defaultJSONEncoder()) throws -> String { 50 | let data = try encoder.encode(value) 51 | return try String.from(data: data, encoding: encoding) 52 | } 53 | } 54 | 55 | extension Keyed: Encodable where Base: Encodable { 56 | public func encode(to encoder: Encoder) throws { 57 | if let keyedEncoder = encoder as? KeyedEncoder { 58 | try value.encode(to: keyedEncoder) 59 | } else { 60 | try value.encode(to: KeyedEncoder(encoder: encoder, codingPath: encoder.codingPath, cache: nil)) 61 | } 62 | } 63 | } 64 | 65 | // MARK: internal 66 | 67 | struct KeyedEncoder: Encoder { 68 | 69 | let encoder: Encoder 70 | let codingPath: [CodingKey] 71 | 72 | let cache: (() -> KeyedEncodingContainerCache)? 73 | 74 | var userInfo: [CodingUserInfoKey : Any] { return encoder.userInfo } 75 | 76 | func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { 77 | let cache = self.cache?() ?? { 78 | let cache = KeyedEncodingContainerCache(encoder: encoder) 79 | _ = cache.container // always initialize cotainer for top-level cache 80 | return cache 81 | }() 82 | return KeyedEncodingContainer(KeyedKeyedEncodingContainer(keyedEncoder: self, cache: cache)) 83 | } 84 | 85 | func unkeyedContainer() -> UnkeyedEncodingContainer { 86 | return KeyedUnkeyedEncodingContainer(keyedEncoder: self) 87 | } 88 | 89 | func singleValueContainer() -> SingleValueEncodingContainer { 90 | return KeyedSingleValueEncodingContainer(keyedEncoder: self) 91 | } 92 | } 93 | 94 | struct KeyedSingleValueEncodingContainer: SingleValueEncodingContainer { 95 | private(set) var container: SingleValueEncodingContainer 96 | let keyedEncoder: KeyedEncoder 97 | 98 | var codingPath: [CodingKey] { return keyedEncoder.codingPath } 99 | 100 | init(keyedEncoder: KeyedEncoder) { 101 | self.keyedEncoder = keyedEncoder 102 | container = keyedEncoder.encoder.singleValueContainer() 103 | } 104 | 105 | mutating func encodeNil() throws { 106 | try container.encodeNil() 107 | } 108 | 109 | mutating func encode(_ value: Bool) throws { 110 | try container.encode(value) 111 | } 112 | 113 | mutating func encode(_ value: String) throws { 114 | try container.encode(value) 115 | } 116 | 117 | mutating func encode(_ value: Double) throws { 118 | try container.encode(value) 119 | } 120 | 121 | mutating func encode(_ value: Float) throws { 122 | try container.encode(value) 123 | } 124 | 125 | mutating func encode(_ value: Int) throws { 126 | try container.encode(value) 127 | } 128 | 129 | mutating func encode(_ value: Int8) throws { 130 | try container.encode(value) 131 | } 132 | 133 | mutating func encode(_ value: Int16) throws { 134 | try container.encode(value) 135 | } 136 | 137 | mutating func encode(_ value: Int32) throws { 138 | try container.encode(value) 139 | } 140 | 141 | mutating func encode(_ value: Int64) throws { 142 | try container.encode(value) 143 | } 144 | 145 | mutating func encode(_ value: UInt) throws { 146 | try container.encode(value) 147 | } 148 | 149 | mutating func encode(_ value: UInt8) throws { 150 | try container.encode(value) 151 | } 152 | 153 | mutating func encode(_ value: UInt16) throws { 154 | try container.encode(value) 155 | } 156 | 157 | mutating func encode(_ value: UInt32) throws { 158 | try container.encode(value) 159 | } 160 | 161 | mutating func encode(_ value: UInt64) throws { 162 | try container.encode(value) 163 | } 164 | 165 | mutating func encode(_ value: T) throws where T : Encodable { 166 | try value.encode(to: keyedEncoder) 167 | } 168 | } 169 | 170 | struct KeyedUnkeyedEncodingContainer: UnkeyedEncodingContainer { 171 | 172 | let keyedEncoder: KeyedEncoder 173 | private(set) var container: UnkeyedEncodingContainer 174 | 175 | var codingPath: [CodingKey] { return keyedEncoder.codingPath } 176 | 177 | init(keyedEncoder: KeyedEncoder) { 178 | self.keyedEncoder = keyedEncoder 179 | container = keyedEncoder.encoder.unkeyedContainer() 180 | } 181 | 182 | var count: Int { return container.count } 183 | 184 | mutating func encodeNil() throws { 185 | try container.encodeNil() 186 | } 187 | 188 | mutating func encode(_ value: String) throws { 189 | try container.encode(value) 190 | } 191 | 192 | mutating func encode(_ value: Double) throws { 193 | try container.encode(value) 194 | } 195 | 196 | mutating func encode(_ value: Float) throws { 197 | try container.encode(value) 198 | } 199 | 200 | mutating func encode(_ value: Int) throws { 201 | try container.encode(value) 202 | } 203 | 204 | mutating func encode(_ value: Int8) throws { 205 | try container.encode(value) 206 | } 207 | 208 | mutating func encode(_ value: Int16) throws { 209 | try container.encode(value) 210 | } 211 | 212 | mutating func encode(_ value: Int32) throws { 213 | try container.encode(value) 214 | } 215 | 216 | mutating func encode(_ value: Int64) throws { 217 | try container.encode(value) 218 | } 219 | 220 | mutating func encode(_ value: UInt) throws { 221 | try container.encode(value) 222 | } 223 | 224 | mutating func encode(_ value: UInt8) throws { 225 | try container.encode(value) 226 | } 227 | 228 | mutating func encode(_ value: UInt16) throws { 229 | try container.encode(value) 230 | } 231 | 232 | mutating func encode(_ value: UInt32) throws { 233 | try container.encode(value) 234 | } 235 | 236 | mutating func encode(_ value: UInt64) throws { 237 | try container.encode(value) 238 | } 239 | 240 | mutating func encode(_ value: Bool) throws { 241 | try container.encode(value) 242 | } 243 | 244 | mutating func encode(_ value: T) throws where T : Encodable { 245 | return try value.encode(to: superEncoder()) 246 | } 247 | 248 | mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { 249 | 250 | let encoder = superEncoder() as! KeyedEncoder 251 | return encoder.container(keyedBy: NestedKey.self) 252 | } 253 | 254 | mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { 255 | let encoder = superEncoder() as! KeyedEncoder 256 | return encoder.unkeyedContainer() 257 | } 258 | 259 | mutating func superEncoder() -> Encoder { 260 | let encoder = container.superEncoder() 261 | let lastPath = encoder.codingPath[encoder.codingPath.count - 1] 262 | return KeyedEncoder(encoder: encoder, codingPath: keyedEncoder.codingPath + [lastPath], cache: nil) 263 | } 264 | } 265 | 266 | final class KeyedKeyedEncodingContainer: KeyedEncodingContainerProtocol { 267 | 268 | private let keyedEncoder: KeyedEncoder 269 | private(set) var cache: KeyedEncodingContainerCache 270 | 271 | init(keyedEncoder: KeyedEncoder, cache: KeyedEncodingContainerCache) { 272 | self.keyedEncoder = keyedEncoder 273 | self.cache = cache 274 | } 275 | 276 | var codingPath: [CodingKey] { return keyedEncoder.codingPath } 277 | 278 | private func keyPath(forKey key: K) -> [AnyKey] { 279 | guard let _ = K.self as? AnyKeyedKey.Type else { return [AnyKey(key: key)] } 280 | return key.keyed 281 | } 282 | 283 | func encodeNil(forKey key: K) throws { 284 | try cache.encodeNil(for: keyPath(forKey: key)) 285 | } 286 | 287 | func encode(_ value: Bool, forKey key: K) throws { 288 | try cache.encode(value, for: keyPath(forKey: key)) 289 | } 290 | 291 | func encode(_ value: String, forKey key: K) throws { 292 | try cache.encode(value, for: keyPath(forKey: key)) 293 | } 294 | 295 | func encode(_ value: Double, forKey key: K) throws { 296 | try cache.encode(value, for: keyPath(forKey: key)) 297 | } 298 | 299 | func encode(_ value: Float, forKey key: K) throws { 300 | try cache.encode(value, for: keyPath(forKey: key)) 301 | } 302 | 303 | func encode(_ value: Int, forKey key: K) throws { 304 | try cache.encode(value, for: keyPath(forKey: key)) 305 | } 306 | 307 | func encode(_ value: Int8, forKey key: K) throws { 308 | try cache.encode(value, for: keyPath(forKey: key)) 309 | } 310 | 311 | func encode(_ value: Int16, forKey key: K) throws { 312 | try cache.encode(value, for: keyPath(forKey: key)) 313 | } 314 | 315 | func encode(_ value: Int32, forKey key: K) throws { 316 | try cache.encode(value, for: keyPath(forKey: key)) 317 | } 318 | 319 | func encode(_ value: Int64, forKey key: K) throws { 320 | try cache.encode(value, for: keyPath(forKey: key)) 321 | } 322 | 323 | func encode(_ value: UInt, forKey key: K) throws { 324 | try cache.encode(value, for: keyPath(forKey: key)) 325 | } 326 | 327 | func encode(_ value: UInt8, forKey key: K) throws { 328 | try cache.encode(value, for: keyPath(forKey: key)) 329 | } 330 | 331 | func encode(_ value: UInt16, forKey key: K) throws { 332 | try cache.encode(value, for: keyPath(forKey: key)) 333 | } 334 | 335 | func encode(_ value: UInt32, forKey key: K) throws { 336 | try cache.encode(value, for: keyPath(forKey: key)) 337 | } 338 | 339 | func encode(_ value: UInt64, forKey key: K) throws { 340 | try cache.encode(value, for: keyPath(forKey: key)) 341 | } 342 | 343 | func encode(_ value: T, forKey key: K) throws where T : Encodable { 344 | #if swift(>=5.1) 345 | if (value as? Nullable)?.isNil == true { 346 | // do not encode null as standard library 347 | } else if let type = T.self as? FlatType.Type, !type.isArray { 348 | try value.encode(to: keyedEncoder) 349 | } else { 350 | try value.encode(to: superEncoder(forKey: key)) 351 | } 352 | #else 353 | try value.encode(to: superEncoder(forKey: key)) 354 | #endif 355 | } 356 | 357 | func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: K) -> KeyedEncodingContainer where NestedKey : CodingKey { 358 | 359 | let encoder = superEncoder() as! KeyedEncoder 360 | return encoder.container(keyedBy: NestedKey.self) 361 | } 362 | 363 | func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer { 364 | let encoder = superEncoder() as! KeyedEncoder 365 | return encoder.unkeyedContainer() 366 | } 367 | 368 | func superEncoder() -> Encoder { 369 | let encoder = cache.superEncoder(for: [AnyKey.superKey]) 370 | 371 | return KeyedEncoder(encoder: encoder, codingPath: codingPath + [AnyKey.superKey]) { [cache] in 372 | return cache.node(for: [AnyKey.superKey]) // TODO check with superEncoder for: keyPatch 373 | } 374 | } 375 | 376 | func superEncoder(forKey key: K) -> Encoder { 377 | if key.isFlat { 378 | return keyedEncoder 379 | } else if key.isFirstFlat { 380 | let encoder = cache.superEncoder(for: Array(key.keyed.dropFirst())) 381 | let nodeKey = keyPath(forKey: key) 382 | return KeyedEncoder(encoder: encoder, codingPath: codingPath + [key]) { [cache] in 383 | return cache.node(for: nodeKey) 384 | } 385 | } else { 386 | let encoder = cache.superEncoder(for: keyPath(forKey: key)) 387 | let nodeKey = keyPath(forKey: key) 388 | return KeyedEncoder(encoder: encoder, codingPath: codingPath + [key]) { [cache] in 389 | return cache.node(for: nodeKey) 390 | } 391 | } 392 | } 393 | } 394 | 395 | final class KeyedEncodingContainerCache { 396 | private(set) lazy var container: KeyedEncodingContainer = { 397 | return self.encoder.container(keyedBy: AnyKey.self) 398 | }() 399 | 400 | private(set) var encoder: Encoder 401 | 402 | private(set) var dictionary: [AnyKey: KeyedEncodingContainerCache] = [:] 403 | 404 | // private(set) var encoder: Encoder 405 | // init(encoder: Encoder) { 406 | // self.encoder = encoder 407 | // } 408 | 409 | var _codingPath: [CodingKey] { return encoder.codingPath } 410 | 411 | init(encoder: Encoder) { 412 | self.encoder = encoder 413 | } 414 | 415 | func node(for keyPath: [AnyKey]) -> KeyedEncodingContainerCache { 416 | if keyPath.isEmpty { 417 | return self 418 | } 419 | var keyPath = keyPath 420 | let key = keyPath.removeFirst() 421 | 422 | return node(for: key).node(for: keyPath) 423 | } 424 | 425 | private func node(for key: AnyKey) -> KeyedEncodingContainerCache { 426 | if let cached = dictionary[key] { 427 | return cached 428 | } 429 | 430 | //print(self.container.codingPath) 431 | let encoder = container.superEncoder(forKey: key) 432 | let node = KeyedEncodingContainerCache(encoder: encoder) 433 | dictionary[key] = node 434 | return node 435 | } 436 | 437 | 438 | //========= 439 | private func keyWithPath(for keyPath: [AnyKey]) -> ([AnyKey], AnyKey) { 440 | //guard keyPath.count > 0 else { fatalError("shouldne be like that")} 441 | var keyPath = keyPath 442 | let key = keyPath.removeLast() 443 | return (keyPath, key) 444 | } 445 | func encodeNil(for keyPath: [AnyKey]) throws { 446 | let kP = keyWithPath(for: keyPath) 447 | return try node(for: kP.0).container.encodeNil(forKey: kP.1) 448 | } 449 | 450 | func encode(_ value: Bool, for keyPath: [AnyKey]) throws { 451 | let kP = keyWithPath(for: keyPath) 452 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 453 | } 454 | 455 | func encode(_ value: String, for keyPath: [AnyKey]) throws { 456 | let kP = keyWithPath(for: keyPath) 457 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 458 | } 459 | 460 | func encode(_ value: Double, for keyPath: [AnyKey]) throws { 461 | let kP = keyWithPath(for: keyPath) 462 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 463 | } 464 | 465 | func encode(_ value: Float, for keyPath: [AnyKey]) throws { 466 | let kP = keyWithPath(for: keyPath) 467 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 468 | } 469 | 470 | func encode(_ value: Int, for keyPath: [AnyKey]) throws { 471 | let kP = keyWithPath(for: keyPath) 472 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 473 | } 474 | 475 | func encode(_ value: Int8, for keyPath: [AnyKey]) throws { 476 | let kP = keyWithPath(for: keyPath) 477 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 478 | } 479 | 480 | func encode(_ value: Int16, for keyPath: [AnyKey]) throws { 481 | let kP = keyWithPath(for: keyPath) 482 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 483 | } 484 | 485 | func encode(_ value: Int32, for keyPath: [AnyKey]) throws { 486 | let kP = keyWithPath(for: keyPath) 487 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 488 | } 489 | 490 | func encode(_ value: Int64, for keyPath: [AnyKey]) throws { 491 | let kP = keyWithPath(for: keyPath) 492 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 493 | } 494 | 495 | func encode(_ value: UInt, for keyPath: [AnyKey]) throws { 496 | let kP = keyWithPath(for: keyPath) 497 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 498 | } 499 | 500 | func encode(_ value: UInt8, for keyPath: [AnyKey]) throws { 501 | let kP = keyWithPath(for: keyPath) 502 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 503 | } 504 | 505 | func encode(_ value: UInt16, for keyPath: [AnyKey]) throws { 506 | let kP = keyWithPath(for: keyPath) 507 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 508 | } 509 | 510 | func encode(_ value: UInt32, for keyPath: [AnyKey]) throws { 511 | let kP = keyWithPath(for: keyPath) 512 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 513 | } 514 | 515 | func encode(_ value: UInt64, for keyPath: [AnyKey]) throws { 516 | let kP = keyWithPath(for: keyPath) 517 | return try node(for: kP.0).container.encode(value, forKey: kP.1) 518 | } 519 | 520 | func superEncoder(for keyPath: [AnyKey]) -> Encoder { 521 | let kP = keyWithPath(for: keyPath) 522 | return node(for: kP.0).container.superEncoder(forKey: kP.1) 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/KeyedKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyedKey.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 01/05/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol KeyedKey: CaseIterable, CaseIterableKey, AnyKeyedKey { } 11 | 12 | public protocol AnyKeyedKey: CodingKey { 13 | 14 | var options: KeyOptions? { get } 15 | } 16 | 17 | public protocol CaseIterableKey: CodingKey { 18 | 19 | static var allKeys: [CodingKey] { get } 20 | } 21 | 22 | extension KeyedKey { 23 | public static var allKeys: [CodingKey] { 24 | return Array(allCases) 25 | } 26 | 27 | public var options: KeyOptions? { return nil } 28 | } 29 | 30 | public struct KeyOptions: Hashable { 31 | public var flat: Flat 32 | public var delimiter: Delimiter 33 | 34 | public init(delimiter: Delimiter = KeyedConfig.default.keyOptions.delimiter, 35 | flat: Flat = KeyedConfig.default.keyOptions.flat) { 36 | self.flat = flat 37 | self.delimiter = delimiter 38 | } 39 | 40 | public enum Delimiter: Hashable { 41 | case none 42 | case character(_ character: Character) 43 | } 44 | 45 | public enum Flat: Hashable { 46 | case none 47 | case emptyOrWhitespace 48 | case string(_ string: String) 49 | 50 | public func isFlat(key: CodingKey) -> Bool { 51 | switch self { 52 | case .none: return false 53 | case .emptyOrWhitespace: return key.intValue == nil && key.stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty 54 | case .string(let string): return key.stringValue == string 55 | } 56 | } 57 | } 58 | } 59 | 60 | #if swift(>=5.1) 61 | @propertyWrapper 62 | public struct Flat: Decodable { 63 | public var wrappedValue: T 64 | 65 | public init(wrappedValue: T) { 66 | self.wrappedValue = wrappedValue 67 | } 68 | 69 | public init(from decoder: Decoder) throws { 70 | if let type = T.self as? _Array.Type { 71 | let unkeyed = try decoder.unkeyedContainer() 72 | wrappedValue = type.optionalDecode(unkeyedContainer: unkeyed) 73 | } else if let type = T.self as? _Optional.Type { 74 | guard let value = try? T(from: decoder) else { 75 | wrappedValue = type.empty as! T 76 | return 77 | } 78 | wrappedValue = value 79 | } else { 80 | wrappedValue = try T(from: decoder) 81 | } 82 | } 83 | } 84 | 85 | extension Flat: Encodable where T: Encodable { 86 | public func encode(to encoder: Encoder) throws { 87 | try wrappedValue.encode(to: encoder) 88 | } 89 | } 90 | 91 | protocol FlatType { 92 | static var isArray: Bool { get } 93 | } 94 | extension Flat: FlatType { 95 | static var isArray: Bool { 96 | return T.self is _Array.Type 97 | } 98 | } 99 | extension Flat: Nullable { 100 | var isNil: Bool { 101 | guard let wrapped = wrappedValue as? _Optional else { return false } 102 | return wrapped.isNil 103 | } 104 | } 105 | #endif 106 | 107 | public struct Keyed { 108 | 109 | @available(*, deprecated, renamed: "Base") 110 | typealias Value = Base 111 | 112 | public let value: Base 113 | 114 | public init(_ value: Base) { 115 | self.value = value 116 | } 117 | } 118 | 119 | public enum KeyedCodableError: Error { 120 | case stringParseFailed 121 | case transformFailed 122 | } 123 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/Transformers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transformers.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 05/10/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | #if swift(>=5.1) 11 | public protocol DecodableTransformer { 12 | associatedtype Source: Decodable 13 | associatedtype Object 14 | 15 | static func transform(from decodable: Source) throws -> Any? 16 | } 17 | 18 | public protocol EncodableTransformer { 19 | associatedtype Destination: Encodable 20 | associatedtype Object 21 | 22 | static func transform(object: Object) throws -> Destination? 23 | } 24 | 25 | public protocol Transformer: DecodableTransformer, EncodableTransformer where Source == Destination { } 26 | 27 | 28 | @propertyWrapper 29 | public struct EncodedBy: Encodable { 30 | public var wrappedValue: T.Object 31 | 32 | public init(wrappedValue: T.Object) { 33 | self.wrappedValue = wrappedValue 34 | } 35 | 36 | public func encode(to encoder: Encoder) throws { 37 | try encodeObject(object: wrappedValue, transType: T.self, encoder: encoder) 38 | } 39 | } 40 | 41 | @propertyWrapper 42 | public struct DecodedBy: Decodable { 43 | public var wrappedValue: T.Object 44 | 45 | public init(wrappedValue: T.Object) { 46 | self.wrappedValue = wrappedValue 47 | } 48 | 49 | public init(from decoder: Decoder) throws { 50 | wrappedValue = try decodeObject(transType: T.self, decoder: decoder) 51 | } 52 | } 53 | 54 | @propertyWrapper 55 | public struct CodedBy: Codable { 56 | public var wrappedValue: T.Object 57 | 58 | public init(wrappedValue: T.Object) { 59 | self.wrappedValue = wrappedValue 60 | } 61 | 62 | public init(from decoder: Decoder) throws { 63 | wrappedValue = try decodeObject(transType: T.self, decoder: decoder) 64 | } 65 | 66 | public func encode(to encoder: Encoder) throws { 67 | try encodeObject(object: wrappedValue, transType: T.self, encoder: encoder) 68 | } 69 | } 70 | 71 | private func encodeObject(object: T.Object, transType: T.Type, encoder: Encoder) 72 | throws where T: EncodableTransformer { 73 | let encodable = try T.transform(object: object) 74 | var container = encoder.singleValueContainer() 75 | try container.encode(encodable) 76 | } 77 | 78 | private func decodeObject(transType: T.Type, decoder: Decoder) 79 | throws -> T.Object where T: DecodableTransformer{ 80 | 81 | let source: T.Source 82 | 83 | if let type = T.Object.self as? _Optional.Type, !(T.Source.self is _Optional.Type) { 84 | guard let s = try Optional(from: decoder) else { 85 | return type.empty as! T.Object 86 | } 87 | 88 | source = s 89 | } else { 90 | source = try T.Source(from: decoder) 91 | } 92 | 93 | guard let value = try T.transform(from: source) as? T.Object else { 94 | throw KeyedCodableError.transformFailed 95 | } 96 | return value 97 | } 98 | 99 | extension EncodedBy: Nullable { 100 | var isNil: Bool { 101 | guard let wrapped = wrappedValue as? _Optional else { return false } 102 | return wrapped.isNil 103 | } 104 | } 105 | extension CodedBy: Nullable { 106 | var isNil: Bool { 107 | guard let wrapped = wrappedValue as? _Optional else { return false } 108 | return wrapped.isNil 109 | } 110 | } 111 | 112 | protocol Nullable { 113 | var isNil: Bool { get } 114 | } 115 | 116 | protocol _Optional: Nullable { 117 | static var empty: _Optional { get } 118 | } 119 | 120 | extension Optional: _Optional { 121 | static var empty: _Optional { 122 | return self.none as _Optional 123 | } 124 | 125 | var isNil: Bool { 126 | switch self { 127 | case .none: return true 128 | default: return false 129 | } 130 | } 131 | } 132 | #endif 133 | -------------------------------------------------------------------------------- /KeyedCodable/Sources/ZeroDecodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZeroDecodable.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 26/03/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | #if swift(>=5.1) 11 | @propertyWrapper 12 | public struct Zero: Decodable { 13 | public var wrappedValue: T 14 | 15 | public init(wrappedValue: T) { 16 | self.wrappedValue = wrappedValue 17 | } 18 | 19 | public init(from decoder: Decoder) throws { 20 | guard let container = try? decoder.singleValueContainer(), 21 | let value = try? container.decode(T.self) else { 22 | 23 | wrappedValue = try T.keyed.zero() 24 | return 25 | } 26 | wrappedValue = value 27 | } 28 | } 29 | 30 | extension Zero: Encodable where T: Encodable { 31 | public func encode(to encoder: Encoder) throws { 32 | var container = encoder.singleValueContainer() 33 | try container.encode(wrappedValue) 34 | } 35 | } 36 | 37 | extension Zero: Nullable { 38 | var isNil: Bool { 39 | guard let wrapped = wrappedValue as? _Optional else { return false } 40 | return wrapped.isNil 41 | } 42 | } 43 | #endif 44 | 45 | extension Keyed where Base: Decodable { 46 | public static func zero() throws -> Base { 47 | return try ZeroDecoder().decode(Base.self) 48 | } 49 | } 50 | 51 | struct ZeroDecoder { 52 | 53 | var userInfo: [CodingUserInfoKey : Any] = [:] 54 | 55 | public func decode(_ type: T.Type) throws -> T { 56 | let decoder: Decoder = ZeroSingleValueDecoder(minimumMode: true, codingPath: [], userInfo: userInfo) 57 | return try T(from: decoder) 58 | } 59 | } 60 | 61 | struct ZeroSingleValueDecoder: Decoder { 62 | 63 | let minimumMode: Bool 64 | 65 | var codingPath: [CodingKey] 66 | 67 | var userInfo: [CodingUserInfoKey : Any] 68 | 69 | func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { 70 | return KeyedDecodingContainer(ZeroKeyedDecodingContainer(minimumMode: minimumMode, 71 | codingPath: codingPath, 72 | userInfo: userInfo)) 73 | } 74 | 75 | func unkeyedContainer() throws -> UnkeyedDecodingContainer { 76 | return ZeroUnkeyedDecodingContainer(minimumMode: minimumMode, 77 | codingPath: codingPath, 78 | userInfo: userInfo) 79 | } 80 | 81 | func singleValueContainer() throws -> SingleValueDecodingContainer { 82 | return self 83 | } 84 | } 85 | 86 | extension ZeroSingleValueDecoder: SingleValueDecodingContainer { 87 | 88 | func decodeNil() -> Bool { 89 | return !minimumMode 90 | } 91 | 92 | func decode(_ type: Bool.Type) throws -> Bool { 93 | return false 94 | } 95 | 96 | func decode(_ type: String.Type) throws -> String { 97 | return "" 98 | } 99 | 100 | func decode(_ type: Double.Type) throws -> Double { 101 | return 0 102 | } 103 | 104 | func decode(_ type: Float.Type) throws -> Float { 105 | return 0 106 | } 107 | 108 | func decode(_ type: Int.Type) throws -> Int { 109 | return 0 110 | } 111 | 112 | func decode(_ type: Int8.Type) throws -> Int8 { 113 | return 0 114 | } 115 | 116 | func decode(_ type: Int16.Type) throws -> Int16 { 117 | return 0 118 | } 119 | 120 | func decode(_ type: Int32.Type) throws -> Int32 { 121 | return 0 122 | } 123 | 124 | func decode(_ type: Int64.Type) throws -> Int64 { 125 | return 0 126 | } 127 | 128 | func decode(_ type: UInt.Type) throws -> UInt { 129 | return 0 130 | } 131 | 132 | func decode(_ type: UInt8.Type) throws -> UInt8 { 133 | return 0 134 | } 135 | 136 | func decode(_ type: UInt16.Type) throws -> UInt16 { 137 | return 0 138 | } 139 | 140 | func decode(_ type: UInt32.Type) throws -> UInt32 { 141 | return 0 142 | } 143 | 144 | func decode(_ type: UInt64.Type) throws -> UInt64 { 145 | return 0 146 | } 147 | 148 | func decode(_ type: T.Type) throws -> T where T : Decodable { 149 | let decoder: Decoder = ZeroSingleValueDecoder(minimumMode: minimumMode, 150 | codingPath: codingPath, 151 | userInfo: userInfo) 152 | return try T(from: decoder) 153 | } 154 | } 155 | 156 | struct ZeroUnkeyedDecodingContainer: UnkeyedDecodingContainer { 157 | 158 | let minimumMode: Bool 159 | 160 | private(set) var userInfo: [CodingUserInfoKey : Any] 161 | 162 | let codingPath: [CodingKey] 163 | 164 | var count: Int? { return minimumMode ? 0 : 1 } 165 | 166 | var isAtEnd: Bool { return currentIndex >= count ?? 0 } 167 | 168 | private(set) var currentIndex: Int = 0 169 | 170 | init(minimumMode: Bool, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { 171 | self.minimumMode = minimumMode 172 | self.codingPath = codingPath 173 | self.userInfo = userInfo 174 | } 175 | 176 | mutating func decodeNil() throws -> Bool { 177 | return !minimumMode 178 | } 179 | 180 | mutating func decode(_ type: Bool.Type) throws -> Bool { 181 | currentIndex += 1 182 | return false 183 | } 184 | 185 | mutating func decode(_ type: String.Type) throws -> String { 186 | currentIndex += 1 187 | return "" 188 | } 189 | 190 | mutating func decode(_ type: Double.Type) throws -> Double { 191 | currentIndex += 1 192 | return 0 193 | } 194 | 195 | mutating func decode(_ type: Float.Type) throws -> Float { 196 | currentIndex += 1 197 | return 0 198 | } 199 | 200 | mutating func decode(_ type: Int.Type) throws -> Int { 201 | currentIndex += 1 202 | return 0 203 | } 204 | 205 | mutating func decode(_ type: Int8.Type) throws -> Int8 { 206 | currentIndex += 1 207 | return 0 208 | } 209 | 210 | mutating func decode(_ type: Int16.Type) throws -> Int16 { 211 | currentIndex += 1 212 | return 0 213 | } 214 | 215 | mutating func decode(_ type: Int32.Type) throws -> Int32 { 216 | currentIndex += 1 217 | return 0 218 | } 219 | 220 | mutating func decode(_ type: Int64.Type) throws -> Int64 { 221 | currentIndex += 1 222 | return 0 223 | } 224 | 225 | mutating func decode(_ type: UInt.Type) throws -> UInt { 226 | currentIndex += 1 227 | return 0 228 | } 229 | 230 | mutating func decode(_ type: UInt8.Type) throws -> UInt8 { 231 | currentIndex += 1 232 | return 0 233 | } 234 | 235 | mutating func decode(_ type: UInt16.Type) throws -> UInt16 { 236 | currentIndex += 1 237 | return 0 238 | } 239 | 240 | mutating func decode(_ type: UInt32.Type) throws -> UInt32 { 241 | currentIndex += 1 242 | return 0 243 | } 244 | 245 | mutating func decode(_ type: UInt64.Type) throws -> UInt64 { 246 | currentIndex += 1 247 | return 0 248 | } 249 | 250 | mutating func decode(_ type: T.Type) throws -> T where T : Decodable { 251 | let decoder = ZeroSingleValueDecoder(minimumMode: minimumMode, 252 | codingPath: codingPath + [AnyKey(intValue: currentIndex)], 253 | userInfo: userInfo) 254 | currentIndex += 1 255 | return try T(from: decoder) 256 | } 257 | 258 | mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 259 | 260 | let ret = KeyedDecodingContainer(ZeroKeyedDecodingContainer(minimumMode: minimumMode, 261 | codingPath: codingPath + [AnyKey(intValue: currentIndex)], 262 | userInfo: userInfo)) 263 | currentIndex += 1 264 | return ret 265 | } 266 | 267 | mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { 268 | let ret = ZeroUnkeyedDecodingContainer(minimumMode: minimumMode, 269 | codingPath: codingPath + [AnyKey(intValue: currentIndex)], 270 | userInfo: userInfo) 271 | currentIndex += 1 272 | return ret 273 | } 274 | 275 | mutating func superDecoder() throws -> Decoder { 276 | let ret = ZeroSingleValueDecoder(minimumMode: minimumMode, 277 | codingPath: codingPath + [AnyKey(intValue: currentIndex)], 278 | userInfo: userInfo) 279 | currentIndex += 1 280 | return ret 281 | } 282 | } 283 | 284 | struct ZeroKeyedDecodingContainer: KeyedDecodingContainerProtocol { 285 | 286 | let minimumMode: Bool 287 | 288 | let codingPath: [CodingKey] 289 | 290 | var userInfo: [CodingUserInfoKey : Any] 291 | 292 | var allKeys: [K] { return [] } 293 | 294 | func contains(_ key: K) -> Bool { 295 | return !minimumMode 296 | } 297 | 298 | func decodeNil(forKey key: K) throws -> Bool { 299 | return !minimumMode 300 | } 301 | 302 | func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { 303 | return false 304 | } 305 | 306 | func decode(_ type: String.Type, forKey key: K) throws -> String { 307 | return "" 308 | } 309 | 310 | func decode(_ type: Double.Type, forKey key: K) throws -> Double { 311 | return 0 312 | } 313 | 314 | func decode(_ type: Float.Type, forKey key: K) throws -> Float { 315 | return 0 316 | } 317 | 318 | func decode(_ type: Int.Type, forKey key: K) throws -> Int { 319 | return 0 320 | } 321 | 322 | func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { 323 | return 0 324 | } 325 | 326 | func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { 327 | return 0 328 | } 329 | 330 | func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { 331 | return 0 332 | } 333 | 334 | func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { 335 | return 0 336 | } 337 | 338 | func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { 339 | return 0 340 | } 341 | 342 | func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { 343 | return 0 344 | } 345 | 346 | func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { 347 | return 0 348 | } 349 | 350 | func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { 351 | return 0 352 | } 353 | 354 | func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { 355 | return 0 356 | } 357 | 358 | func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { 359 | let decoder = ZeroSingleValueDecoder(minimumMode: minimumMode, 360 | codingPath: codingPath + [key], 361 | userInfo: userInfo) 362 | return try T(from: decoder) 363 | } 364 | 365 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 366 | 367 | return KeyedDecodingContainer(ZeroKeyedDecodingContainer(minimumMode: minimumMode, codingPath: codingPath + [key], userInfo: userInfo)) 368 | } 369 | 370 | func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { 371 | 372 | return ZeroUnkeyedDecodingContainer(minimumMode: minimumMode, 373 | codingPath: codingPath + [key], 374 | userInfo: userInfo) 375 | } 376 | 377 | func superDecoder() throws -> Decoder { 378 | return ZeroSingleValueDecoder(minimumMode: minimumMode, 379 | codingPath: codingPath + [AnyKey(stringValue: "super")], 380 | userInfo: userInfo) 381 | } 382 | 383 | func superDecoder(forKey key: K) throws -> Decoder { 384 | return ZeroSingleValueDecoder(minimumMode: minimumMode, 385 | codingPath: codingPath + [key], 386 | userInfo: userInfo) 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /KeyedCodableTests/AllKeysTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AllKeysTests.swift 3 | // KeyedCodableTests 4 | // 5 | // Created by Dariusz Grzeszczak on 09/04/2018. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | private let jsonString = """ 12 | { 13 | "vault": { 14 | "0": { 15 | "type": "Braintree_CreditCard", 16 | "_attributes": { 17 | "default": false 18 | } 19 | }, 20 | "1": { 21 | "type": "Braintree_CreditCard", 22 | "_attributes": { 23 | "default": true 24 | } 25 | }, 26 | "2": { 27 | "type": "Braintree_PayPalAccount", 28 | "_attributes": { 29 | "default": false 30 | } 31 | }, 32 | "autoTopup": "9969dc" 33 | } 34 | } 35 | """ 36 | 37 | struct PaymentMethod: Decodable { 38 | 39 | let type: String 40 | let isDefault: Bool 41 | 42 | public enum CodingKeys: String, KeyedKey { 43 | case type 44 | case isDefault = "_attributes.default" 45 | } 46 | } 47 | 48 | struct PaymentMethods: Decodable { 49 | 50 | let userPaymentMethods: [PaymentMethod] 51 | let autoTopUpToken: String? 52 | 53 | enum CodingKeys: String, KeyedKey { 54 | case autoTopUpToken = "vault.autoTopup" 55 | case vault 56 | } 57 | 58 | public init(from decoder: Decoder) throws { 59 | 60 | let container = try decoder.container(keyedBy: CodingKeys.self) 61 | autoTopUpToken = try container.decode(String.self, forKey: .autoTopUpToken) 62 | 63 | let universalContainer = try container.nestedContainer(keyedBy: AnyKey.self, forKey: .vault) 64 | userPaymentMethods = universalContainer.allKeys 65 | .sorted { $0.stringValue < $1.stringValue } 66 | .compactMap { try? universalContainer.decode(PaymentMethod.self, forKey: $0) } 67 | } 68 | } 69 | 70 | class AllKeysTests: XCTestCase { 71 | 72 | override func setUp() { 73 | super.setUp() 74 | // Put setup code here. This method is called before the invocation of each test method in the class. 75 | } 76 | 77 | override func tearDown() { 78 | // Put teardown code here. This method is called after the invocation of each test method in the class. 79 | super.tearDown() 80 | } 81 | 82 | func testAllKeys() { 83 | let jsonData = jsonString.data(using: .utf8)! 84 | 85 | var test: PaymentMethods! 86 | do { 87 | test = try KeyedJSONDecoder().decode(PaymentMethods.self, from: jsonData) 88 | } catch ( let error ) { 89 | XCTFail("PaymentMethods cannot be parsed") 90 | print(error.localizedDescription) 91 | return 92 | } 93 | 94 | XCTAssert(test.userPaymentMethods.count == 3) 95 | XCTAssert(test.userPaymentMethods[1].type == "Braintree_CreditCard") 96 | XCTAssert(test.userPaymentMethods[1].isDefault == true) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /KeyedCodableTests/DecodePathTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodePathTest.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 25/07/2019. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | struct Token: Codable { 12 | 13 | static var keyPath: String = "data" 14 | static var key: AnyKey { 15 | return AnyKey(stringValue: keyPath) 16 | } 17 | 18 | public let accessToken: String 19 | public let expiresIn: Int 20 | public let refreshToken: String 21 | 22 | public enum CodingKeys: String, CodingKey { 23 | case accessToken = "access.token" 24 | case expiresIn = "expires_in" 25 | case refreshToken = "refresh_token" 26 | 27 | static var container = "data" 28 | } 29 | } 30 | 31 | struct TokenContainer: Codable { 32 | public private(set) var token: Token 33 | 34 | public init(from decoder: Decoder) throws { 35 | 36 | let container = try decoder.container(keyedBy: AnyKey.self) 37 | token = try container.decode(Token.self, forKey: Token.key) 38 | } 39 | 40 | func encode(to encoder: Encoder) throws { 41 | var container = encoder.container(keyedBy: AnyKey.self) 42 | try container.encodeIfPresent(token, forKey: Token.key) 43 | } 44 | } 45 | 46 | class DecodePathTests: XCTestCase { 47 | 48 | func testTokenMapping() { 49 | 50 | let innerDataTokenJSON = """ 51 | { 52 | "data": { 53 | "access.token": "access", 54 | "expires_in": 2, 55 | "refresh_token": "refresh" 56 | } 57 | } 58 | """.data(using: .utf8)! 59 | 60 | KeyedCodableTestHelper.checkEncode(data: innerDataTokenJSON, checkString: false) { (test: TokenContainer) in 61 | 62 | XCTAssert(test.token.accessToken == "access") 63 | XCTAssert(test.token.expiresIn == 2) 64 | XCTAssert(test.token.refreshToken == "refresh") 65 | } 66 | 67 | let tokenJSONString = """ 68 | { 69 | "access.token": "access", 70 | "expires_in": 2, 71 | "refresh_token": "refresh" 72 | } 73 | """.data(using: .utf8)! 74 | 75 | Token.keyPath = "" 76 | 77 | KeyedCodableTestHelper.checkEncode(data: tokenJSONString, checkString: false) { (test: TokenContainer) in 78 | 79 | XCTAssert(test.token.accessToken == "access") 80 | XCTAssert(test.token.expiresIn == 2) 81 | XCTAssert(test.token.refreshToken == "refresh") 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /KeyedCodableTests/ExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionTests.swift 3 | // KeyedCodableTests-iOS 4 | // 5 | // Created by Dariusz Grzeszczak on 12/09/2019. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | struct Model: Codable { 12 | var property: Int 13 | } 14 | 15 | let singleJson = """ 16 | { 17 | "property":3 18 | } 19 | """ 20 | 21 | let arrayJson = """ 22 | [ 23 | {"property":3}, 24 | {"property":4}, 25 | {"property":5} 26 | ] 27 | """ 28 | 29 | let valueArrayJson = """ 30 | [1, 2, 3] 31 | """ 32 | 33 | let intJson = "3" 34 | let floatJson = "3.3" 35 | let stringJson = "domek" 36 | 37 | class ExtensionTests: XCTestCase { 38 | 39 | override func setUp() { 40 | super.setUp() 41 | // Put setup code here. This method is called before the invocation of each test method in the class. 42 | } 43 | 44 | override func tearDown() { 45 | // Put teardown code here. This method is called after the invocation of each test method in the class. 46 | super.tearDown() 47 | } 48 | 49 | func testSingle() throws { 50 | var test = try? Model.keyed.fromJSON(singleJson) 51 | XCTAssert(test?.property == 3) 52 | 53 | let data = try test.keyed.jsonData() 54 | test = try? Model.keyed.fromJSON(data) 55 | XCTAssert(test?.property == 3) 56 | } 57 | 58 | func testArray() throws { 59 | var test = try! [Model].keyed.fromJSON(arrayJson) 60 | XCTAssert(test[0].property == 3) 61 | 62 | let data = try test.keyed.jsonData() 63 | test = try [Model].keyed.fromJSON(data) 64 | XCTAssert(test[0].property == 3) 65 | } 66 | 67 | func testValueArray() throws { 68 | var test = try! [Int].keyed.fromJSON(valueArrayJson) 69 | XCTAssert(test[1] == 2) 70 | 71 | let data = try test.keyed.jsonData() 72 | test = try [Int].keyed.fromJSON(data) 73 | XCTAssert(test[1] == 2) 74 | } 75 | 76 | func testIntValue() throws { 77 | var test = try! Int.keyed.fromJSON(intJson) 78 | XCTAssert(test == 3) 79 | 80 | let data = try test.keyed.jsonData() 81 | test = try Int.keyed.fromJSON(data) 82 | XCTAssert(test == 3) 83 | } 84 | 85 | func testFloatValue() throws { 86 | var test = try! Float.keyed.fromJSON(floatJson) 87 | XCTAssert(test == 3.3) 88 | 89 | let data = try test.keyed.jsonData() 90 | test = try Float.keyed.fromJSON(data) 91 | XCTAssert(test == 3.3) 92 | } 93 | 94 | func testStringValue() throws { 95 | var test = try! String.keyed.fromJSON(stringJson) 96 | XCTAssert(test == "domek") 97 | 98 | let data = try test.keyed.jsonData() 99 | test = try String.keyed.fromJSON(data) 100 | XCTAssert(test == "domek") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /KeyedCodableTests/FlatTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlatTests.swift 3 | // KeyedCodableTests 4 | // 5 | // Created by Dariusz Grzeszczak on 11/05/2018. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | private let jsonString = """ 12 | { 13 | "inner": { 14 | "greeting": "hallo" 15 | }, 16 | "longitude": 3.2, 17 | "latitude": 3.4 18 | } 19 | """ 20 | 21 | struct Location: Codable { 22 | let latitude: Double 23 | let longitude: Double? 24 | } 25 | 26 | struct InnerWithFlatExample: Codable { 27 | let greeting: String 28 | let location: Location? 29 | 30 | enum CodingKeys: String, KeyedKey { 31 | case greeting = "inner.greeting" 32 | case location = "" 33 | } 34 | } 35 | 36 | #if swift(>=5.1) 37 | struct InnerWithFlatWrapperExample: Codable { 38 | let greeting: String 39 | @Flat private(set) var location: Location? 40 | 41 | enum CodingKeys: String, KeyedKey { 42 | case greeting = "inner.greeting" 43 | case location 44 | } 45 | } 46 | #endif 47 | 48 | class FlatTests: XCTestCase { 49 | 50 | override func setUp() { 51 | super.setUp() 52 | // Put setup code here. This method is called before the invocation of each test method in the class. 53 | } 54 | 55 | override func tearDown() { 56 | // Put teardown code here. This method is called after the invocation of each test method in the class. 57 | super.tearDown() 58 | } 59 | 60 | func testFlat() { 61 | let jsonData = jsonString.data(using: .utf8)! 62 | 63 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: InnerWithFlatExample) in 64 | XCTAssert(test.greeting == "hallo") 65 | XCTAssert(test.location?.latitude == 3.4) 66 | XCTAssert(test.location?.longitude == 3.2) 67 | } 68 | } 69 | 70 | #if swift(>=5.1) 71 | func testFlatWrapper() { 72 | let jsonData = jsonString.data(using: .utf8)! 73 | 74 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: InnerWithFlatWrapperExample) in 75 | XCTAssert(test.greeting == "hallo") 76 | XCTAssert(test.location?.latitude == 3.4) 77 | XCTAssert(test.location?.longitude == 3.2) 78 | } 79 | } 80 | #endif 81 | } 82 | -------------------------------------------------------------------------------- /KeyedCodableTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /KeyedCodableTests/InnerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InnerTests.swift 3 | // KeyedCodableTests 4 | // 5 | // Created by Dariusz Grzeszczak on 11/05/2018. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | private let jsonString = """ 12 | { 13 | "inner": { 14 | "greeting": "Hallo world", 15 | "details": { 16 | "description": "Its nice here" 17 | } 18 | } 19 | } 20 | """ 21 | 22 | struct StandardExample: Codable { 23 | let inner: Inner 24 | 25 | struct Inner: Codable { 26 | let greeting: String 27 | let details: Details 28 | } 29 | 30 | struct Details: Codable { 31 | let description: String 32 | } 33 | } 34 | 35 | struct InnerExample: Codable { 36 | let greeting: String 37 | let description: String 38 | 39 | enum CodingKeys: String, KeyedKey { 40 | case greeting = "inner.greeting" 41 | case description = "inner.details.description" 42 | } 43 | } 44 | 45 | struct InnerExample1: Codable { 46 | 47 | let greeting: String 48 | let description: String 49 | let details: Details 50 | 51 | struct Details: Codable { 52 | enum CodingKeys: String, KeyedKey { 53 | case description = "details.description" 54 | } 55 | let description: String 56 | } 57 | 58 | enum CodingKeys: String, KeyedKey { 59 | case greeting = "inner.greeting" 60 | case description = "inner.details.description" 61 | case details = "inner" 62 | } 63 | } 64 | 65 | 66 | //MARK: second 67 | struct User: Codable { 68 | var name: String? 69 | } 70 | 71 | typealias OrderDataResponse = ApiResponse 72 | 73 | struct OrderData: Codable { 74 | var orderCount: Int 75 | } 76 | 77 | struct ApiResponse: Codable { 78 | // MARK: Properties 79 | var result: String 80 | var user: User? 81 | var address: String 82 | var data: DataType? 83 | 84 | enum CodingKeys: String, KeyedKey { 85 | case result 86 | case user = "data.user" 87 | case address = "data.user.address" 88 | case data 89 | } 90 | } 91 | 92 | let orderDataResponseJson = """ 93 | { 94 | "result":"OK", 95 | "data":{ 96 | "user": { 97 | "name":"John", 98 | "address":"New York" 99 | }, 100 | "orderCount":10 101 | } 102 | } 103 | """ 104 | 105 | struct Empty: Codable { } 106 | struct NilOnly: Codable { 107 | let nilValue: Int? 108 | } 109 | 110 | class InnerTests: XCTestCase { 111 | 112 | override func setUp() { 113 | super.setUp() 114 | // Put setup code here. This method is called before the invocation of each test method in the class. 115 | } 116 | 117 | override func tearDown() { 118 | // Put teardown code here. This method is called after the invocation of each test method in the class. 119 | super.tearDown() 120 | } 121 | 122 | func testEmpty() throws { 123 | let jsonData = "{}".data(using: .utf8)! 124 | KeyedCodableTestHelper.checkEncode(data: jsonData) { (test: Empty) in } 125 | } 126 | 127 | func testNilEncoding() throws { 128 | let value = NilOnly(nilValue: nil) 129 | let stringValue = try value.keyed.jsonString() 130 | XCTAssert(stringValue == "{}") 131 | } 132 | 133 | func testEmptyNil() { 134 | let jsonData = "{}".data(using: .utf8)! 135 | KeyedCodableTestHelper.checkEncode(data: jsonData) { (test: NilOnly) in } 136 | } 137 | 138 | func testInner() { 139 | let jsonData = jsonString.data(using: .utf8)! 140 | 141 | KeyedCodableTestHelper.checkEncode(data: jsonData) { (test: InnerExample) in 142 | XCTAssert(test.greeting == "Hallo world") 143 | XCTAssert(test.description == "Its nice here") 144 | } 145 | } 146 | 147 | func testResponse() { 148 | let jsonData = orderDataResponseJson.data(using: .utf8)! 149 | 150 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: OrderDataResponse) in 151 | XCTAssert(test.result == "OK") 152 | XCTAssert(test.address == "New York") 153 | XCTAssert(test.user?.name == "John") 154 | XCTAssert(test.data?.orderCount == 10) 155 | } 156 | } 157 | 158 | func testStandard() { 159 | let jsonData = jsonString.data(using: .utf8)! 160 | 161 | KeyedCodableTestHelper.checkEncode(data: jsonData) { (test: StandardExample) in 162 | XCTAssert(test.inner.greeting == "Hallo world") 163 | XCTAssert(test.inner.details.description == "Its nice here") 164 | } 165 | } 166 | 167 | func testInner1() { 168 | let jsonData = jsonString.data(using: .utf8)! 169 | 170 | KeyedCodableTestHelper.checkEncode(data: jsonData) { (test: InnerExample1) in 171 | XCTAssert(test.greeting == "Hallo world") 172 | XCTAssert(test.description == "Its nice here") 173 | XCTAssert(test.details.description == "Its nice here") 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /KeyedCodableTests/KeyOptionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyOptionsTests.swift 3 | // KeyedCodableTests 4 | // 5 | // Created by Dariusz Grzeszczak on 11/05/2018. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | //********** KeyOptions 12 | // sometimes you may need to disable some of the futures above or change it's behaviour eg. dot may be used in json property identifie. 13 | // You can configure them by KeyOptions value to the maping. 14 | 15 | private let jsonString = """ 16 | { 17 | "* name": "John", 18 | "": { 19 | ".greeting": "Hallo world", 20 | "details": { 21 | "description": "Its nice here" 22 | } 23 | }, 24 | "longitude": 3.2, 25 | "latitude": 3.4, 26 | "array": [ 27 | { 28 | "element": 1 29 | }, 30 | {}, 31 | { 32 | "element": 3 33 | }, 34 | { 35 | "element": 4 36 | } 37 | ], 38 | "* array1": [ 39 | { 40 | "element": 1 41 | }, 42 | {}, 43 | { 44 | "element": 3 45 | }, 46 | { 47 | "element": 4 48 | } 49 | ] 50 | } 51 | """ 52 | 53 | struct KeyOptionsExample: Codable { 54 | let greeting: String 55 | let description: String 56 | let name: String 57 | let location: Location 58 | let array: [ArrayElement] 59 | let array1: [ArrayElement] 60 | 61 | 62 | enum CodingKeys: String, KeyedKey { 63 | case location = "__" 64 | case name = "* name" 65 | case greeting = "+.greeting" 66 | case description = ".details.description" 67 | 68 | case array = "### .array" 69 | case array1 = "### .* array1" 70 | 71 | var options: KeyOptions? { 72 | switch self { 73 | case .greeting: return KeyOptions(delimiter: .character("+"), flat: .none) 74 | case .description: return KeyOptions(flat: .none) 75 | case .location: return KeyOptions(flat: .string("__")) 76 | case .array, .array1: return KeyOptions(flat: .string("### ")) 77 | default: return nil 78 | } 79 | } 80 | } 81 | } 82 | 83 | class KeyOptionsTests: XCTestCase { 84 | 85 | override func setUp() { 86 | super.setUp() 87 | // Put setup code here. This method is called before the invocation of each test method in the class. 88 | } 89 | 90 | override func tearDown() { 91 | // Put teardown code here. This method is called after the invocation of each test method in the class. 92 | super.tearDown() 93 | } 94 | 95 | func testKeyOptions() { 96 | let jsonData = jsonString.data(using: .utf8)! 97 | 98 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: KeyOptionsExample) in 99 | 100 | XCTAssert(test.name == "John") 101 | 102 | XCTAssert(test.greeting == "Hallo world") 103 | XCTAssert(test.description == "Its nice here") 104 | 105 | XCTAssert(test.location.latitude == 3.4) 106 | XCTAssert(test.location.longitude == 3.2) 107 | 108 | XCTAssert(test.array.count == 3) 109 | XCTAssert(test.array[0].element == 1) 110 | XCTAssert(test.array[1].element == 3) 111 | XCTAssert(test.array[2].element == 4) 112 | 113 | XCTAssert(test.array1.count == 3) 114 | XCTAssert(test.array1[0].element == 1) 115 | XCTAssert(test.array1[1].element == 3) 116 | XCTAssert(test.array1[2].element == 4) 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /KeyedCodableTests/KeyedCodableTests.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// KeyedCodableTests.swift 3 | //// KeyedCodableTests 4 | //// 5 | //// Created by Dariusz Grzeszczak on 26/03/2018. 6 | //// 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | struct KeyedCodableTestHelper { 12 | 13 | static func checkEncode(data: Data, checkString: Bool = true, testObject: (_ codable: Type) -> Void) { 14 | 15 | let origString = String(data: data, encoding: .utf8) 16 | 17 | let codable: Type 18 | 19 | do { 20 | codable = try Type.keyed.fromJSON(data) 21 | } catch { 22 | XCTFail("\(Type.self) cannot be parsed with error: \(error)") 23 | return 24 | } 25 | 26 | testObject(codable) 27 | 28 | guard let jsonString = try? codable.keyed.jsonString() else { 29 | XCTFail("\(Type.self) cannot be encoded") 30 | return 31 | } 32 | 33 | if checkString { 34 | XCTAssert(jsonString.removedWhiteSpaces == origString?.removedWhiteSpaces, 35 | "Result JSON string is different") // check the string are the same 36 | } 37 | 38 | guard let object = try? Type.keyed.fromJSON(jsonString) else { 39 | XCTFail("\(Type.self) cannot be parsed second time") 40 | return 41 | } 42 | 43 | testObject(object) 44 | 45 | guard let jsonString1 = try? object.keyed.jsonString() else { 46 | XCTFail("\(Type.self) cannot be encoded second time") 47 | return 48 | } 49 | 50 | XCTAssert(jsonString == jsonString1) 51 | } 52 | } 53 | 54 | extension String { 55 | var removedWhiteSpaces: String { 56 | let characters = compactMap { 57 | $0 == " " || $0 == "\t" || $0 == "\n" ? nil : $0 58 | } 59 | return String(characters.sorted()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /KeyedCodableTests/OptionalArrayElementTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalArrayElementTests.swift 3 | // KeyedCodableTests 4 | // 5 | // Created by Dariusz Grzeszczak on 11/05/2018. 6 | // 7 | 8 | import XCTest 9 | import KeyedCodable 10 | 11 | // by default parsing all array will fail when any of element fail 12 | // you can mark array an optional - so it will ommit element that couldn't be parsed (eg. some field missing) 13 | 14 | private let jsonString = """ 15 | { 16 | "array": [ 17 | { 18 | "element": 1 19 | }, 20 | {}, 21 | { 22 | "element": 3 23 | }, 24 | { 25 | "element": 4 26 | } 27 | ] 28 | } 29 | """ 30 | 31 | struct ArrayElement: Codable { 32 | let element: Int 33 | } 34 | 35 | struct OptionalArrayElementsExample: Codable { 36 | let array: [ArrayElement] 37 | 38 | enum CodingKeys: String, KeyedKey { 39 | case array = ".array" 40 | } 41 | } 42 | 43 | #if swift(>=5.1) 44 | struct OptionalArrayElementsWrapperExample: Codable { 45 | 46 | @Flat private(set) var array: [ArrayElement] 47 | 48 | } 49 | #endif 50 | 51 | class OptionalArrayElementTests: XCTestCase { 52 | 53 | override func setUp() { 54 | super.setUp() 55 | // Put setup code here. This method is called before the invocation of each test method in the class. 56 | } 57 | 58 | override func tearDown() { 59 | // Put teardown code here. This method is called after the invocation of each test method in the class. 60 | super.tearDown() 61 | } 62 | 63 | func testOptionalArrayElement() { 64 | let jsonData = jsonString.data(using: .utf8)! 65 | 66 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: OptionalArrayElementsExample) in 67 | // returns array with 3 elements, empty element will be omitted 68 | XCTAssert(test.array.count == 3) 69 | XCTAssert(test.array[0].element == 1) 70 | XCTAssert(test.array[1].element == 3) 71 | XCTAssert(test.array[2].element == 4) 72 | } 73 | } 74 | 75 | #if swift(>=5.1) 76 | func testOptionalArrayElementWrapper() { 77 | let jsonData = jsonString.data(using: .utf8)! 78 | 79 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: OptionalArrayElementsWrapperExample) in 80 | // returns array with 3 elements, empty element will be omitted 81 | XCTAssert(test.array.count == 3) 82 | XCTAssert(test.array[0].element == 1) 83 | XCTAssert(test.array[1].element == 3) 84 | XCTAssert(test.array[2].element == 4) 85 | } 86 | } 87 | #endif 88 | } 89 | -------------------------------------------------------------------------------- /KeyedCodableTests/TransformersTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformersTests.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 06/10/2019. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | #if swift(>=5.1) 12 | 13 | let notCodable = """ 14 | { 15 | "some": 3 16 | } 17 | """ 18 | struct NotCodable { 19 | let id: Int 20 | } 21 | 22 | enum NonCodableTransformer: Transformer { 23 | static func transform(from decodable: Int) -> Any? { 24 | return NotCodable(id: decodable) 25 | } 26 | 27 | static func transform(object: Object) -> Int? { 28 | return (object as? NotCodable)?.id 29 | } 30 | } 31 | 32 | struct SomeCodable: Codable { 33 | 34 | @CodedBy var some: NotCodable 35 | } 36 | 37 | 38 | //Dates 39 | enum DateTransformer: Transformer { 40 | 41 | static var formatter: DateFormatter { 42 | let formatter = DateFormatter() 43 | formatter.dateFormat = "yyyy-MM-dd" 44 | return formatter 45 | } 46 | 47 | static func transform(from decodable: String) -> Any? { 48 | return formatter.date(from: decodable) 49 | } 50 | 51 | static func transform(object: Object) -> String? { 52 | guard let object = object as? Date else { return nil } 53 | return formatter.string(from: object) 54 | } 55 | } 56 | 57 | let dates = """ 58 | { 59 | "date0": "2012-05-01", 60 | "date1": "2012-05-02", 61 | "date2": "2012-05-03" 62 | } 63 | """ 64 | 65 | struct DateCodableTrasform: Codable { 66 | 67 | @CodedBy var date0: Date! 68 | @CodedBy var date1: Date 69 | @CodedBy var date2: Date? 70 | @CodedBy var date3: Date? 71 | } 72 | 73 | struct DateCodableTransform: Codable { 74 | @CodedBy var date4: Date! 75 | } 76 | 77 | class TransformersTests: XCTestCase { 78 | 79 | func testTransformDates() throws { 80 | let jsonData = dates.data(using: .utf8)! 81 | 82 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: DateCodableTrasform) in 83 | XCTAssert(test.date0.timeIntervalSince1970 == 1335823200) 84 | XCTAssert(test.date1 == Date(timeIntervalSince1970: 1335909600)) 85 | XCTAssert(test.date2 == Date(timeIntervalSince1970: 1335996000)) 86 | XCTAssert(test.date3 == nil) 87 | } 88 | } 89 | 90 | func testTransformOptionalNilEncoding() throws { 91 | 92 | let date = try DateCodableTransform.keyed.fromJSON("{}") 93 | let string = try date.keyed.jsonString() 94 | XCTAssert(string == "{}") 95 | } 96 | 97 | func testTransformNotCodable() { 98 | let jsonData = notCodable.data(using: .utf8)! 99 | 100 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: SomeCodable) in 101 | XCTAssert(test.some.id == 3) 102 | } 103 | } 104 | } 105 | #endif 106 | -------------------------------------------------------------------------------- /KeyedCodableTests/ZeroDecodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZeroDecodableTests.swift 3 | // KeyedCodable 4 | // 5 | // Created by Dariusz Grzeszczak on 06/10/2019. 6 | // 7 | 8 | import KeyedCodable 9 | import XCTest 10 | 11 | struct ZeroTestCodable: Codable { 12 | @Zero var property: Int 13 | } 14 | 15 | struct ZeroTestOptionalCodable: Codable { 16 | @Zero var property: Int? 17 | } 18 | 19 | struct ZeroTestImplicitCodable: Decodable { 20 | @Zero var property: Int? 21 | } 22 | 23 | class ZeroDecodableTests: XCTestCase { 24 | 25 | override func setUp() { 26 | // Put setup code here. This method is called before the invocation of each test method in the class. 27 | } 28 | 29 | override func tearDown() { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | } 32 | 33 | func testStandardExample() throws { 34 | let zero = try StandardExample.keyed.zero() 35 | XCTAssert(zero.inner.details.description == "") 36 | XCTAssert(zero.inner.greeting == "") 37 | } 38 | 39 | #if swift(>=5.1) 40 | func testStandardExampleWrapper() throws { 41 | let zero = try ZeroTestCodable.keyed.fromJSON("{ }") 42 | XCTAssert(zero.property == 0) 43 | } 44 | 45 | func testNilZeroWrapper() throws { 46 | let zero = try ZeroTestOptionalCodable.keyed.fromJSON("{ }") 47 | XCTAssert(zero.property == nil) 48 | } 49 | 50 | func testNilInpicitZeroWrapper() throws { 51 | let zero = try ZeroTestImplicitCodable.keyed.fromJSON("{ }") 52 | XCTAssert(zero.property == nil) 53 | } 54 | 55 | func testEncoding() throws { 56 | let jsonData = "{}".data(using: .utf8)! 57 | KeyedCodableTestHelper.checkEncode(data: jsonData, checkString: false) { (test: ZeroTestCodable) in 58 | XCTAssert(test.property == 0) 59 | } 60 | } 61 | 62 | func testNilEncoding() throws { 63 | let zero = try ZeroTestOptionalCodable.keyed.fromJSON("{}") 64 | let string = try zero.keyed.jsonString() 65 | XCTAssert(string == "{}") 66 | } 67 | #endif 68 | } 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dariusz Grzeszczak 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | // TODO: resolve problem with sources shared to all targets 7 | 8 | let package = Package( 9 | name: "KeyedCodable", 10 | // platforms: [.macOS(.v10_10), .iOS(.v9), .tvOS(.v9), .watchOS(.v2)], 11 | platforms: [.iOS(.v9)], 12 | products: [ 13 | .library( 14 | name: "KeyedCodable", 15 | // targets: ["KeyedCodable-iOS", "KeyedCodable-watchOS", "KeyedCodable-tvOS", "KeyedCodable-macOS"] 16 | targets: ["KeyedCodable"] 17 | ), 18 | ], 19 | targets: [ 20 | .target(name: "KeyedCodable", path: "KeyedCodable/Sources"), 21 | // .target(name: "KeyedCodable-watchOS", path: "KeyedCodable/Sources"), 22 | // .target(name: "KeyedCodable-tvOS", path: "KeyedCodable/Sources"), 23 | // .target(name: "KeyedCodable-macOS", path: "KeyedCodable/Sources") 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 3 | [![Version](https://img.shields.io/cocoapods/v/KeyedCodable.svg?style=flat)](https://cocoapods.org/pods/KeyedCodable) 4 | [![License](https://img.shields.io/cocoapods/l/KeyedCodable.svg?style=flat)](https://cocoapods.org/pods/KeyedCodable) 5 | [![Platform](https://img.shields.io/cocoapods/p/KeyedCodable.svg?style=flat)](https://cocoapods.org/pods/KeyedCodable) 6 | 7 | ![](/keyedCodable.gif) 8 | 9 | # The goal 10 | 11 | *KeyedCodable* is an addition to swift's *Codable* and it's designed for automatic nested key mappings. The goal it to avoid manual implementation of *Encodable/Decodable* and make encoding/decoding easier, more readable, less boilerplate and what is the most important fully compatible with 'standard' *Codable*. 12 | 13 | # Nested keys 14 | First, please have a look on Codable example provided by Apple. 15 | ### vanilla Codable: 16 | ```swift 17 | struct Coordinate { 18 | var latitude: Double 19 | var longitude: Double 20 | var elevation: Double 21 | 22 | enum CodingKeys: String, CodingKey { 23 | case latitude 24 | case longitude 25 | case additionalInfo 26 | } 27 | 28 | enum AdditionalInfoKeys: String, CodingKey { 29 | case elevation 30 | } 31 | } 32 | 33 | extension Coordinate: Decodable { 34 | init(from decoder: Decoder) throws { 35 | let values = try decoder.container(keyedBy: CodingKeys.self) 36 | latitude = try values.decode(Double.self, forKey: .latitude) 37 | longitude = try values.decode(Double.self, forKey: .longitude) 38 | 39 | let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo) 40 | elevation = try additionalInfo.decode(Double.self, forKey: .elevation) 41 | } 42 | } 43 | 44 | extension Coordinate: Encodable { 45 | func encode(to encoder: Encoder) throws { 46 | var container = encoder.container(keyedBy: CodingKeys.self) 47 | try container.encode(latitude, forKey: .latitude) 48 | try container.encode(longitude, forKey: .longitude) 49 | 50 | var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo) 51 | try additionalInfo.encode(elevation, forKey: .elevation) 52 | } 53 | } 54 | ``` 55 | 56 | ### KeyedCodable: 57 | ```swift 58 | struct Coordinate: Codable { 59 | var latitude: Double 60 | var longitude: Double 61 | var elevation: Double 62 | 63 | enum CodingKeys: String, KeyedKey { 64 | case latitude 65 | case longitude 66 | case elevation = "additionalInfo.elevation" 67 | } 68 | } 69 | ``` 70 | 71 | *By default dot is used as delimiter to separate the inner keys* 72 | 73 | # @Flat classes 74 | Flat classes feature allows you to group properties into smaller parts. In the example below you can see that: 75 | - longitude and latitude are placed in json's main class but it is 'moved' into separate struct ```Location``` in the swift's model 76 | - both longitude and latitude are optional. If any of them is missing, the location property will be nil. 77 | 78 | ### Example JSON: 79 | ```json 80 | { 81 | "longitude": 3.2, 82 | "latitude": 3.4, 83 | 84 | "greeting": "hallo" 85 | } 86 | ``` 87 | 88 | ### KeyedCodable: 89 | ```swift 90 | struct InnerWithFlatExample: Codable { 91 | @Flat var location: Location? 92 | 93 | let greeting: String 94 | } 95 | 96 | struct Location: Codable { 97 | let latitude: Double 98 | let longitude: Double 99 | } 100 | ``` 101 | 102 |
103 | Without property wrappers 104 | 105 | ```swift 106 | struct InnerWithFlatExample: Codable { 107 | let location: Location? 108 | 109 | let greeting: String 110 | 111 | enum CodingKeys: String, KeyedKey { 112 | case greeting 113 | case location = "" 114 | } 115 | } 116 | ``` 117 | 118 | *By default empty string or whitespaces are used to mark flat class* 119 |
120 | 121 | # @Flat arrays 122 | Probably you found out that decoding an array will fail if decoding of any array's element fails. Flat array *KeyedCodable's* feature omits incorrect elements and creates array that contain valid elements only. In example below ```array``` property will contain three elements [1,3,4] though decoding second element fails. 123 | 124 | ```json 125 | { 126 | "array": [ 127 | { 128 | "element": 1 129 | }, 130 | {}, 131 | { 132 | "element": 3 133 | }, 134 | { 135 | "element": 4 136 | } 137 | ] 138 | } 139 | ``` 140 | ### KeyedCodable: 141 | ```swift 142 | struct OptionalArrayElementsExample: Codable { 143 | @Flat var array: [ArrayElement] 144 | } 145 | 146 | struct ArrayElement: Codable { 147 | let element: Int 148 | } 149 | ``` 150 | 151 |
152 | Without property wrappers 153 | 154 | ```swift 155 | struct OptionalArrayElementsExample: Codable { 156 | let array: [ArrayElement] 157 | 158 | enum CodingKeys: String, KeyedKey { 159 | case array = ".array" 160 | } 161 | } 162 | ``` 163 | 164 | *To enable flat array you have to add [flat][delimiter] before property name - by defult it is 'empty string + dot'* 165 |
166 | 167 | # KeyOptions 168 | 169 | In case of conflicts between json's keys and delimiters used by ```KeyedCodable```, you may use ```KeyOptions``` to configure mapping features. You may do that by providing ```options: KeyOptions?``` property in your CodingKeys ( use ```nil``` to use default value). You may also disable the feature by setting ```.none``` value. 170 | 171 |
172 | Examples 173 | 174 | ### Example JSON: 175 | ```json 176 | { 177 | "* name": "John", 178 | "": { 179 | ".greeting": "Hallo world", 180 | "details": { 181 | "description": "Its nice here" 182 | } 183 | }, 184 | "longitude": 3.2, 185 | "latitude": 3.4, 186 | "array": [ 187 | { 188 | "element": 1 189 | }, 190 | {}, 191 | { 192 | "element": 3 193 | }, 194 | { 195 | "element": 4 196 | } 197 | ], 198 | "* array1": [ 199 | { 200 | "element": 1 201 | }, 202 | {}, 203 | { 204 | "element": 3 205 | }, 206 | { 207 | "element": 4 208 | } 209 | ] 210 | } 211 | ``` 212 | ### KeyedCodable: 213 | ```swift 214 | struct KeyOptionsExample: Codable { 215 | let greeting: String 216 | let description: String 217 | let name: String 218 | let location: Location 219 | let array: [ArrayElement] 220 | let array1: [ArrayElement] 221 | 222 | 223 | enum CodingKeys: String, KeyedKey { 224 | case location = "__" 225 | case name = "* name" 226 | case greeting = "+.greeting" 227 | case description = ".details.description" 228 | 229 | case array = "### .array" 230 | case array1 = "### .* array1" 231 | 232 | var options: KeyOptions? { 233 | switch self { 234 | case .greeting: return KeyOptions(delimiter: .character("+"), flat: .none) 235 | case .description: return KeyOptions(flat: .none) 236 | case .location: return KeyOptions(flat: .string("__")) 237 | case .array, .array1: return KeyOptions(flat: .string("### ")) 238 | default: return nil 239 | } 240 | } 241 | } 242 | } 243 | ``` 244 |
245 | 246 | # @Zero as default value 247 | 248 | @Zero feature sets default "zero" value for the property in case value is not set in the JSON (no key at all or null value is set). If you find the situation there is no difference between 0 and nil from the business perspective you can easily get rid off optional from your codable. 249 | 250 | ### Example JSON: 251 | ```json 252 | { 253 | "name": "Jan" 254 | } 255 | ``` 256 | 257 | ### KeyedCodable: 258 | ```swift 259 | struct ZeroTestCodable: Codable { 260 | var name: String // Jan 261 | 262 | @Zero var secondName: String // "" - empty string 263 | @Zero var numberOfChildren: Int // 0 264 | @Zero var point: Point // Point(x: 0.0, y: 0.0) 265 | } 266 | 267 | struct Point: Codable { 268 | let x: Float 269 | let y: Float 270 | } 271 | ``` 272 | 273 | # Transformers - @CodedBy, @EncodedBy, @DecodedBy 274 | 275 | Have you ever had a need of decoding a JSON having multiple Date formats in it ? Or maybe you had to add custom transformation from one value to another during decode/encode process and you had to write a lot of boilerplate code for manual coding ? Transformers are designed for making custom transformation a lot easier ! 276 | 277 | To add your custom transformer you have to implement ```DecodableTransformer```, ```EncodableTransformer``` or ```Transformer``` for two way transformation. 278 | 279 | ## Date formater example 280 | 281 | ### Example JSON: 282 | ```json 283 | { 284 | "date": "2012-05-01" 285 | } 286 | ``` 287 | 288 | ### Usage 289 | ```swift 290 | struct DateCodableTrasform: Codable { 291 | 292 | @CodedBy var date: Date 293 | } 294 | ``` 295 | 296 | ### Transformer 297 | ```swift 298 | enum DateTransformer: Transformer { 299 | 300 | static func transform(from decodable: String) -> Any? { 301 | return formatter.date(from: decodable) 302 | } 303 | 304 | static func transform(object: Object) -> String? { 305 | guard let object = object as? Date else { return nil } 306 | return formatter.string(from: object) 307 | } 308 | 309 | 310 | private static var formatter: DateFormatter { 311 | let formatter = DateFormatter() 312 | formatter.dateFormat = "yyyy-MM-dd" 313 | return formatter 314 | } 315 | } 316 | ``` 317 | 318 | ## Non codable formater example 319 | 320 | You can even implement transformer for non Codable model and use it in you Codable tree like that: 321 | 322 | ### Example JSON: 323 | ```json 324 | { 325 | "user": 3 326 | } 327 | ``` 328 | 329 | ### Usage 330 | ```swift 331 | struct DetailPage: Codable { 332 | 333 | @CodedBy var user: User 334 | } 335 | 336 | struct User { // User do not implement Codable protocol 337 | let id: Int 338 | } 339 | ``` 340 | 341 | ### Transformer 342 | ```swift 343 | enum UserTransformer: Transformer { 344 | static func transform(from decodable: Int) -> Any? { 345 | return User(id: decodable) 346 | } 347 | 348 | static func transform(object: Object) -> Int? { 349 | return (object as? User)?.id 350 | } 351 | } 352 | ``` 353 | 354 | # How to use? 355 | 356 | To support nested key mappings you need to use ```KeyedKey``` intead of ```CodingKey``` for your ```CodingKeys``` enums and ```KeyedJSONEncoder```/```KeyedJSONDecoder``` in place standard ```JSONEncoder```/```JSONDecoder```. Please notice that *Keyed* coders inherit from standard equivalents so they are fully compatible with Apple versions. 357 | 358 | ### Codable extensions 359 | 360 | ```swift 361 | struct Model: Codable { 362 | var property: Int 363 | } 364 | ``` 365 | Decode from string: 366 | ```swift 367 | let model = try Model.keyed.fromJSON(jsonString) 368 | ``` 369 | Decode from data: 370 | ```swift 371 | let model = try Model.keyed.fromJSON(data) 372 | ``` 373 | Endcode to string: 374 | ```swift 375 | model.keyed.jsonString() 376 | ``` 377 | 378 | Encode to data: 379 | ```swift 380 | model.keyed.jsonData() 381 | ``` 382 | 383 | *You can provide coders in method parameters in case you need additional setup* 384 | 385 | ### Coders 386 | 387 | You can also use *Keyed* coders the same way as standard versions. 388 | 389 | ```swift 390 | let model = try KeyedJSONDecoder().decode(Model.self, from: data) 391 | ``` 392 | ```swift 393 | let data = try KeyedJSONEncoder().encode(model) 394 | ``` 395 | 396 | It's worth to mention that *Keyed* coders supports simple types ie. ```String```, ```Int``` etc. 397 | For example when we try to decode ```Int``` using standard ```JSONDecoder``` 398 | ```swift 399 | let number = try JSONDecoder().decode(Int.self, from: data) 400 | ``` 401 | it will throw an incorrect format error. Keyed version will parse that with success. 402 | 403 | ### Keyed<> wrapper 404 | 405 | There is a possibility to use standard JSON coders and still encode/decode *KeyedCodables*. To do that you have to use ```Keyed<>``` wrapper: 406 | ```swift 407 | let model = try JSONDecoder().decode(Keyed.self, from: data).value 408 | ``` 409 | ```swift 410 | let data = try JSONEncoder().encode(Keyed(model)) 411 | ``` 412 | 413 | It may be useful in case you do not have an access to coder's initialization code. In that situation your model may looks like that: 414 | 415 | ```swift 416 | struct KeyedModel: Codable { 417 | ... 418 | 419 | enum CodingKeys: String, KeyedKey { 420 | .... 421 | } 422 | .... 423 | } 424 | 425 | struct Model { 426 | 427 | let keyed: Keyed 428 | } 429 | ``` 430 | 431 | ### Manual nested key coding 432 | 433 | In case you need implement *Codable* manually you can use ```AnyKey``` for simpler access to nested keys. 434 | 435 | ```swift 436 | private let TokenKey = AnyKey(stringValue: "data.tokens.access_token") 437 | 438 | struct TokenModel: Codable { 439 | 440 | let token: Token 441 | 442 | init(from decoder: Decoder) throws { 443 | let container = try decoder.container(keyedBy: AnyKey.self) 444 | token = try container.decode(Token.self, forKey: TokenKey) 445 | } 446 | 447 | func encode(to encoder: Encoder) throws { 448 | var container = encoder.container(keyedBy: AnyKey.self) 449 | try container.encode(token, forKey: TokenKey) 450 | } 451 | } 452 | ``` 453 | 454 | ### KeyedConfig 455 | 456 | As mentioned earlier there is possibility to setup key options eg. delimiters on ```KeyedKey``` level but there is also possibility to setup it globally. 457 | 458 | To do that you need to set the value of ```KeyedConfig.default.keyOptions```. 459 | 460 | Beside key opptions there is also possibility to setup coders used by default in *Codable* extensions. 461 | 462 | ## Migration to 3.x.x version 463 | 464 | Version 3.0.0 is backward compatible with version 2.x.x however you have to use **swift 5.1** for all new features connected with ```@PropertyWrapper```s. 465 | 466 | ## Migration to 2.x.x version 467 | 468 | Unfortunately 2.x.x version is not compatible with 1.x.x versions but I believe that new way is much better and it brings less boilerplate than previous versions. There is no need to add any manual mapping implementation, it's really simple so I strongly recommend to migrate to new version. All you need is to: 469 | - use ```KeyedJSONEncoder``` \ ```KeyedJSONDecoder``` instead of ```JSONEncoder``` \ ```JSONDecoder``` !! 470 | - change you CodingKeys to ```KeyedKey``` and move your mappings here 471 | - remove ```KeyedCodable``` protocol 472 | - remove constructor and map method 473 | -------------------------------------------------------------------------------- /keyedCodable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgrzeszczak/KeyedCodable/7e24e26cf3a844ee0dbcdbcc8c78ed45f9570e69/keyedCodable.gif --------------------------------------------------------------------------------