├── .gitignore ├── LICENSE ├── README.md ├── example └── club_sample.json ├── images ├── json2swift.jpg └── show_in_finder.png ├── json2swift.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── json2swift ├── command-line-interface.swift ├── failable-initializer-translation.swift ├── json-attribute-merging.swift ├── json-data-model.swift ├── json-helpers.swift ├── json-schema-inference.swift ├── main.swift ├── name-translation.swift ├── schema-to-struct-translation.swift ├── swift-code-generation.swift ├── swift-code-templates.swift └── swift-data-model.swift └── unit_tests ├── Info.plist ├── JSONType+JSONType.swift ├── json-attribute-merging-boolean-tests.swift ├── json-attribute-merging-element-array-tests.swift ├── json-attribute-merging-element-tests.swift ├── json-attribute-merging-empty-array-tests.swift ├── json-attribute-merging-numeric-tests.swift ├── json-attribute-merging-text-tests.swift ├── json-attribute-merging-value-array-tests.swift ├── json-schema-inference-element-array-tests.swift ├── json-schema-inference-element-tests.swift ├── json-schema-inference-value-array-tests.swift ├── name-translation-tests.swift ├── swift-code-generation-custom-struct-tests.swift ├── swift-code-generation-primitive-array-tests.swift └── swift-code-generation-primitive-value-tests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Josh Smith 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json2swift 2 | 3 | ![Overview](/images/json2swift.jpg) 4 | 5 | A macOS command line tool that generates excellent Swift data models based on JSON data. 6 | 7 | It takes care of the boring error-prone grunt work of consuming JSON data in your app. 8 | 9 | Feel free to modify the code it creates for you. 10 | 11 | Written and unit tested in Swift 4.2. 12 | 13 | ## Features 14 | 15 | - Generates immutable Swift struct definitions 16 | - Generates thread-safe code to create structs from JSON data 17 | - Performs sophisticated type inference to detect URLs, parse dates, etc. 18 | - Creates properties with *required* values whenever possible, but *optional* if necessary 19 | - Processes a single JSON file or a directory of JSON files 20 | 21 | ## What is code generation? 22 | 23 | Using a JSON-to-Swift code generator is very different from using a JSON library API. If you have never worked with a code generator before, check out [this blog post](https://ijoshsmith.com/2016/11/03/swift-json-library-vs-code-generation/) for a quick overview. 24 | 25 | ## How to get it 26 | 27 | - Download the `json2swift` app binary from the latest [release](https://github.com/ijoshsmith/json2swift/releases) 28 | - Copy `json2swift` to your desktop 29 | - Open a Terminal window and run this command to give the app permission to execute: 30 | 31 | ``` 32 | chmod +x ~/Desktop/json2swift 33 | ``` 34 | 35 | Or build the tool in Xcode yourself: 36 | 37 | - Clone the repository / Download the source code 38 | - Build the project 39 | - Open a Finder window to the executable file 40 | 41 | ![How to find the executable](/images/show_in_finder.png) 42 | 43 | - Drag `json2swift` from the Finder window to your desktop 44 | 45 | ## How to install it 46 | 47 | Assuming that the `json2swift` app is on your desktop… 48 | 49 | Open a Terminal window and run this command: 50 | ``` 51 | cp ~/Desktop/json2swift /usr/local/bin/ 52 | ``` 53 | Verify `json2swift` is in your search path by running this in Terminal: 54 | ``` 55 | json2swift 56 | ``` 57 | You should see the tool respond like this: 58 | ``` 59 | Error: Please provide a JSON file path or directory path. 60 | ``` 61 | Now that a copy of `json2swift` is in your search path, delete it from your desktop. 62 | 63 | You're ready to go! 🎉 64 | 65 | ## How to use it 66 | 67 | Open a Terminal window and pass `json2swift` a JSON file path: 68 | ``` 69 | json2swift /path/to/some/data_file.json 70 | ``` 71 | The tool creates a file with the same path as the input file, but with a `.swift` extension. In the example above, the output file is `/path/to/some/data_file.swift`. 72 | 73 | Alternatively, you can create Swift data models for all JSON files in a directory via: 74 | ``` 75 | json2swift /path/to/some/directory/ 76 | ``` 77 | When the tool generates only one Swift file, it includes utility methods that are used for JSON processing in that file. When generating multiple Swift files the utility methods are placed in `JSONUtilities.swift`. 78 | 79 | It is possible to pass a 2nd argument to set the root struct name: 80 | ``` 81 | json2swift /path/to/some/data_file.json app-data 82 | ``` 83 | 84 | The generated struct will then be named `AppData` instead of the default `RootType`. 85 | 86 | The source code download includes an `example` directory with a `club_sample.json` file so that you can test it out. 87 | 88 | For more info to help get started, check out [Generating Models from JSON with json2swift](https://littlebitesofcocoa.com/283-generating-models-from-json-with-json2swift) by [Jake Marsh](https://github.com/jakemarsh). 89 | 90 | ## Structure and property names 91 | 92 | This tool has no reliable way to create good names for the Swift structs it generates. It follows a simple heuristic to convert names found in JSON attributes to Swift-friendly names, and leaves a `// TODO` comment reminding a developer to rename the structs. 93 | 94 | There are precautions in place to ensure that Swift property names are valid. If the generated property names do not meet your needs, rename them as you see fit. Remember, this tool creates a starting point, feel free to change the code it created for you. 95 | 96 | ## Date parsing 97 | 98 | `json2swift` has special support for JSON attributes with formatted date string values. If you provide a date format "hint" it will generate the necessary code to parse the date strings into `Date` objects using the provided format. For example: 99 | ```json 100 | { 101 | "birthday": "1945-12-25" 102 | } 103 | ``` 104 | Before being analyzed by the tool, this JSON should be changed to: 105 | ```json 106 | { 107 | "birthday": "DATE_FORMAT=yyyy-MM-dd" 108 | } 109 | ``` 110 | The resulting Swift data model struct will have a property defined as: 111 | ```swift 112 | let birthday: Date 113 | ``` 114 | and will also have date parsing code that uses the specified date format. 115 | 116 | *Tip: For an array with multiple elements, add the `DATE_FORMAT=` hint to the date attribute of only one element. It isn't necessary to add the hint to every element.* 117 | 118 | ## Type inference 119 | 120 | What sets this JSON-to-Swift converter apart from the others is its type inference capabilities. The net result is Swift code that uses the most appropriate data types possible. This functionality really shines when analyzing an array of elements. 121 | 122 | For example, suppose `json2swift` analyzes this JSON: 123 | ```json 124 | [ 125 | { 126 | "nickname": "Johnson Rod", 127 | "quantity": 8 128 | }, 129 | { 130 | "nickname": null, 131 | "quantity": 10.5 132 | } 133 | ] 134 | ``` 135 | What should be the data types of the `nickname` and `quantity` properties? If this tool only inspected the first element in the array, as other JSON-to-Swift converters do, it would arrive at the wrong answer of `String` and `Int`, respectively. Here is the output of `json2swift` which uses the correct data types for both properties: 136 | ```swift 137 | struct RootType: CreatableFromJSON { 138 | let nickname: String? 139 | let quantity: Double 140 | init(nickname: String?, quantity: Double) { 141 | self.nickname = nickname 142 | self.quantity = quantity 143 | } 144 | init?(json: [String: Any]) { 145 | guard let quantity = Double(json: json, key: "quantity") else { return nil } 146 | let nickname = json["nickname"] as? String 147 | self.init(nickname: nickname, quantity: quantity) 148 | } 149 | } 150 | ``` 151 | Note that `nickname` is an _optional_ `String` and `quantity` is a `Double` (not an `Int`) in order to accommodate the values found in both elements of the sample JSON data. 152 | 153 | The type inference logic can only perform well if the JSON it analyzes has enough information about the data set. If the generated code doesn't work with all possible values that might be encountered in production, feel free to modify it as you see fit. 154 | 155 | ## How does it work? 156 | 157 | For a high-level overview of how this tool works, check out [this brief article](https://ijoshsmith.com/2016/11/10/json2swift-a-peek-under-the-hood/). 158 | -------------------------------------------------------------------------------- /example/club_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "club-name":"Random Person Club", 3 | "headquarters":"New York City", 4 | "members":[ 5 | { 6 | "first-name":"Josh", 7 | "last-name":"Smith", 8 | "birthday":"DATE_FORMAT=yyyy-MM-dd", 9 | "websites":[ 10 | "http://ijoshsmith.com", 11 | "http://joshsmithonwpf.wordpress.com" 12 | ], 13 | "favorite-color":"green", 14 | "inches-tall":78, 15 | "hobbies":[ 16 | { 17 | "type":"athletic", 18 | "name":"cycling" 19 | }, 20 | { 21 | "type":"creative", 22 | "name":"playing music" 23 | }, 24 | { 25 | "type":"intellectual", 26 | "name":"reading books" 27 | } 28 | ] 29 | }, 30 | { 31 | "first-name":"Johnny", 32 | "last-name":"Appleseed", 33 | "birthday":"1776-09-26", 34 | "websites":[ 35 | 36 | ], 37 | "favorite-color":null, 38 | "inches-tall":61.5, 39 | "hobbies":[ 40 | { 41 | "type":"agriculture", 42 | "name":"apples" 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /images/json2swift.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ijoshsmith/json2swift/1445464ba187e94cc36a3fa3ec46aae687860252/images/json2swift.jpg -------------------------------------------------------------------------------- /images/show_in_finder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ijoshsmith/json2swift/1445464ba187e94cc36a3fa3ec46aae687860252/images/show_in_finder.png -------------------------------------------------------------------------------- /json2swift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 032199E01DBC1B2200BF8272 /* schema-to-struct-translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032199DF1DBC1B2200BF8272 /* schema-to-struct-translation.swift */; }; 11 | 032199E11DBC1B2200BF8272 /* schema-to-struct-translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032199DF1DBC1B2200BF8272 /* schema-to-struct-translation.swift */; }; 12 | 032199E31DBC266900BF8272 /* name-translation-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032199E21DBC266900BF8272 /* name-translation-tests.swift */; }; 13 | 03237E031DC58A7B00ECCEF9 /* json-attribute-merging-empty-array-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03237E021DC58A7B00ECCEF9 /* json-attribute-merging-empty-array-tests.swift */; }; 14 | 03524D651DC27A5A0002E0CD /* name-translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03524D641DC27A5A0002E0CD /* name-translation.swift */; }; 15 | 03524D661DC27A5A0002E0CD /* name-translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03524D641DC27A5A0002E0CD /* name-translation.swift */; }; 16 | 036BF2D31DB14C630004C4FC /* swift-code-generation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036BF2D21DB14C630004C4FC /* swift-code-generation.swift */; }; 17 | 036BF2D41DB14C630004C4FC /* swift-code-generation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036BF2D21DB14C630004C4FC /* swift-code-generation.swift */; }; 18 | 036BF2D61DB14CA50004C4FC /* swift-data-model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036BF2D51DB14CA50004C4FC /* swift-data-model.swift */; }; 19 | 036BF2D71DB14CA50004C4FC /* swift-data-model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036BF2D51DB14CA50004C4FC /* swift-data-model.swift */; }; 20 | 03720E461DB177D600BCFE72 /* json-attribute-merging-value-array-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03720E451DB177D600BCFE72 /* json-attribute-merging-value-array-tests.swift */; }; 21 | 03720E481DB17FE400BCFE72 /* JSONType+JSONType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03720E471DB17FE400BCFE72 /* JSONType+JSONType.swift */; }; 22 | 03720E4A1DB18E3D00BCFE72 /* json-schema-inference-value-array-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03720E491DB18E3D00BCFE72 /* json-schema-inference-value-array-tests.swift */; }; 23 | 0392CC791DBA7AFB00289A67 /* swift-code-generation-primitive-value-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CC781DBA7AFB00289A67 /* swift-code-generation-primitive-value-tests.swift */; }; 24 | 0392CC7B1DBA7E7D00289A67 /* swift-code-generation-custom-struct-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CC7A1DBA7E7D00289A67 /* swift-code-generation-custom-struct-tests.swift */; }; 25 | 03A195831DAFEA7A00481B1F /* json-schema-inference-element-array-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A195821DAFEA7A00481B1F /* json-schema-inference-element-array-tests.swift */; }; 26 | 03B0D0C51DAFCD7C00D20E31 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0C41DAFCD7C00D20E31 /* main.swift */; }; 27 | 03B0D0DA1DAFCDD000D20E31 /* json-attribute-merging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0D71DAFCDD000D20E31 /* json-attribute-merging.swift */; }; 28 | 03B0D0DB1DAFCDD000D20E31 /* json-data-model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0D81DAFCDD000D20E31 /* json-data-model.swift */; }; 29 | 03B0D0DC1DAFCDD000D20E31 /* json-schema-inference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0D91DAFCDD000D20E31 /* json-schema-inference.swift */; }; 30 | 03B0D0DD1DAFCDD900D20E31 /* json-attribute-merging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0D71DAFCDD000D20E31 /* json-attribute-merging.swift */; }; 31 | 03B0D0DE1DAFCDD900D20E31 /* json-data-model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0D81DAFCDD000D20E31 /* json-data-model.swift */; }; 32 | 03B0D0DF1DAFCDD900D20E31 /* json-schema-inference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0D91DAFCDD000D20E31 /* json-schema-inference.swift */; }; 33 | 03B0D0E61DAFCDF700D20E31 /* json-attribute-merging-element-array-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0E01DAFCDF700D20E31 /* json-attribute-merging-element-array-tests.swift */; }; 34 | 03B0D0E71DAFCDF700D20E31 /* json-attribute-merging-boolean-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0E11DAFCDF700D20E31 /* json-attribute-merging-boolean-tests.swift */; }; 35 | 03B0D0E81DAFCDF700D20E31 /* json-attribute-merging-element-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0E21DAFCDF700D20E31 /* json-attribute-merging-element-tests.swift */; }; 36 | 03B0D0E91DAFCDF700D20E31 /* json-attribute-merging-numeric-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0E31DAFCDF700D20E31 /* json-attribute-merging-numeric-tests.swift */; }; 37 | 03B0D0EA1DAFCDF700D20E31 /* json-attribute-merging-text-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0E41DAFCDF700D20E31 /* json-attribute-merging-text-tests.swift */; }; 38 | 03B0D0EB1DAFCDF700D20E31 /* json-schema-inference-element-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B0D0E51DAFCDF700D20E31 /* json-schema-inference-element-tests.swift */; }; 39 | 03B52FD71DC3FD9D00910D1B /* command-line-interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B52FD61DC3FD9D00910D1B /* command-line-interface.swift */; }; 40 | 03B52FD81DC3FD9D00910D1B /* command-line-interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B52FD61DC3FD9D00910D1B /* command-line-interface.swift */; }; 41 | 03B89CA81DBAC00B007B9776 /* swift-code-generation-primitive-array-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B89CA71DBAC00B007B9776 /* swift-code-generation-primitive-array-tests.swift */; }; 42 | 03C8303E1DC3B98500999D21 /* failable-initializer-translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C8303D1DC3B98500999D21 /* failable-initializer-translation.swift */; }; 43 | 03C8303F1DC3B98500999D21 /* failable-initializer-translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C8303D1DC3B98500999D21 /* failable-initializer-translation.swift */; }; 44 | 03D815671DB83BDA00B88564 /* swift-code-templates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D815661DB83BDA00B88564 /* swift-code-templates.swift */; }; 45 | 03D815681DB83BDA00B88564 /* swift-code-templates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D815661DB83BDA00B88564 /* swift-code-templates.swift */; }; 46 | 03DCE0AA1DB086C10087787F /* json-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DCE0A91DB086C10087787F /* json-helpers.swift */; }; 47 | 03DCE0AB1DB086C10087787F /* json-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DCE0A91DB086C10087787F /* json-helpers.swift */; }; 48 | /* End PBXBuildFile section */ 49 | 50 | /* Begin PBXCopyFilesBuildPhase section */ 51 | 03B0D0BF1DAFCD7C00D20E31 /* CopyFiles */ = { 52 | isa = PBXCopyFilesBuildPhase; 53 | buildActionMask = 2147483647; 54 | dstPath = /usr/share/man/man1/; 55 | dstSubfolderSpec = 0; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 1; 59 | }; 60 | /* End PBXCopyFilesBuildPhase section */ 61 | 62 | /* Begin PBXFileReference section */ 63 | 032199DF1DBC1B2200BF8272 /* schema-to-struct-translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "schema-to-struct-translation.swift"; sourceTree = ""; }; 64 | 032199E21DBC266900BF8272 /* name-translation-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "name-translation-tests.swift"; sourceTree = ""; }; 65 | 03237E021DC58A7B00ECCEF9 /* json-attribute-merging-empty-array-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "json-attribute-merging-empty-array-tests.swift"; sourceTree = ""; }; 66 | 03524D641DC27A5A0002E0CD /* name-translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "name-translation.swift"; sourceTree = ""; }; 67 | 036BF2D21DB14C630004C4FC /* swift-code-generation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "swift-code-generation.swift"; sourceTree = ""; }; 68 | 036BF2D51DB14CA50004C4FC /* swift-data-model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "swift-data-model.swift"; sourceTree = ""; }; 69 | 03720E451DB177D600BCFE72 /* json-attribute-merging-value-array-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "json-attribute-merging-value-array-tests.swift"; sourceTree = ""; }; 70 | 03720E471DB17FE400BCFE72 /* JSONType+JSONType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONType+JSONType.swift"; sourceTree = ""; }; 71 | 03720E491DB18E3D00BCFE72 /* json-schema-inference-value-array-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "json-schema-inference-value-array-tests.swift"; sourceTree = ""; }; 72 | 0392CC781DBA7AFB00289A67 /* swift-code-generation-primitive-value-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "swift-code-generation-primitive-value-tests.swift"; sourceTree = ""; }; 73 | 0392CC7A1DBA7E7D00289A67 /* swift-code-generation-custom-struct-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "swift-code-generation-custom-struct-tests.swift"; sourceTree = ""; }; 74 | 03A195821DAFEA7A00481B1F /* json-schema-inference-element-array-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-schema-inference-element-array-tests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 75 | 03B0D0C11DAFCD7C00D20E31 /* json2swift */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = json2swift; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | 03B0D0C41DAFCD7C00D20E31 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = main.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 77 | 03B0D0CF1DAFCD9C00D20E31 /* unit_tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = unit_tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 78 | 03B0D0D31DAFCD9C00D20E31 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 79 | 03B0D0D71DAFCDD000D20E31 /* json-attribute-merging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-attribute-merging.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 80 | 03B0D0D81DAFCDD000D20E31 /* json-data-model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-data-model.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 81 | 03B0D0D91DAFCDD000D20E31 /* json-schema-inference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-schema-inference.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 82 | 03B0D0E01DAFCDF700D20E31 /* json-attribute-merging-element-array-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-attribute-merging-element-array-tests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 83 | 03B0D0E11DAFCDF700D20E31 /* json-attribute-merging-boolean-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-attribute-merging-boolean-tests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 84 | 03B0D0E21DAFCDF700D20E31 /* json-attribute-merging-element-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-attribute-merging-element-tests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 85 | 03B0D0E31DAFCDF700D20E31 /* json-attribute-merging-numeric-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-attribute-merging-numeric-tests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 86 | 03B0D0E41DAFCDF700D20E31 /* json-attribute-merging-text-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-attribute-merging-text-tests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 87 | 03B0D0E51DAFCDF700D20E31 /* json-schema-inference-element-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-schema-inference-element-tests.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 88 | 03B52FD61DC3FD9D00910D1B /* command-line-interface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "command-line-interface.swift"; sourceTree = ""; }; 89 | 03B89CA71DBAC00B007B9776 /* swift-code-generation-primitive-array-tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "swift-code-generation-primitive-array-tests.swift"; sourceTree = ""; }; 90 | 03C8303D1DC3B98500999D21 /* failable-initializer-translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "failable-initializer-translation.swift"; sourceTree = ""; }; 91 | 03D815661DB83BDA00B88564 /* swift-code-templates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "swift-code-templates.swift"; sourceTree = ""; }; 92 | 03DCE0A91DB086C10087787F /* json-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "json-helpers.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 93 | /* End PBXFileReference section */ 94 | 95 | /* Begin PBXFrameworksBuildPhase section */ 96 | 03B0D0BE1DAFCD7C00D20E31 /* Frameworks */ = { 97 | isa = PBXFrameworksBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | ); 101 | runOnlyForDeploymentPostprocessing = 0; 102 | }; 103 | 03B0D0CC1DAFCD9C00D20E31 /* Frameworks */ = { 104 | isa = PBXFrameworksBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXFrameworksBuildPhase section */ 111 | 112 | /* Begin PBXGroup section */ 113 | 0328715A1DC53BF6002E4584 /* JSON Data --> JSON Schema */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 03DCE0A91DB086C10087787F /* json-helpers.swift */, 117 | 03B0D0D81DAFCDD000D20E31 /* json-data-model.swift */, 118 | 03B0D0D71DAFCDD000D20E31 /* json-attribute-merging.swift */, 119 | 03B0D0D91DAFCDD000D20E31 /* json-schema-inference.swift */, 120 | ); 121 | name = "JSON Data --> JSON Schema"; 122 | sourceTree = ""; 123 | }; 124 | 0328715B1DC53C30002E4584 /* Swift Model --> Swift Source Code */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 036BF2D51DB14CA50004C4FC /* swift-data-model.swift */, 128 | 03D815661DB83BDA00B88564 /* swift-code-templates.swift */, 129 | 036BF2D21DB14C630004C4FC /* swift-code-generation.swift */, 130 | ); 131 | name = "Swift Model --> Swift Source Code"; 132 | sourceTree = ""; 133 | }; 134 | 0328715C1DC53C3F002E4584 /* JSON Schema --> Swift Model */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 03524D641DC27A5A0002E0CD /* name-translation.swift */, 138 | 03C8303D1DC3B98500999D21 /* failable-initializer-translation.swift */, 139 | 032199DF1DBC1B2200BF8272 /* schema-to-struct-translation.swift */, 140 | ); 141 | name = "JSON Schema --> Swift Model"; 142 | sourceTree = ""; 143 | }; 144 | 03B0D0B81DAFCD7C00D20E31 = { 145 | isa = PBXGroup; 146 | children = ( 147 | 03B0D0C31DAFCD7C00D20E31 /* json2swift */, 148 | 03B0D0D01DAFCD9C00D20E31 /* unit_tests */, 149 | 03B0D0C21DAFCD7C00D20E31 /* products */, 150 | ); 151 | sourceTree = ""; 152 | }; 153 | 03B0D0C21DAFCD7C00D20E31 /* products */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 03B0D0C11DAFCD7C00D20E31 /* json2swift */, 157 | 03B0D0CF1DAFCD9C00D20E31 /* unit_tests.xctest */, 158 | ); 159 | name = products; 160 | sourceTree = ""; 161 | }; 162 | 03B0D0C31DAFCD7C00D20E31 /* json2swift */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 0328715A1DC53BF6002E4584 /* JSON Data --> JSON Schema */, 166 | 0328715C1DC53C3F002E4584 /* JSON Schema --> Swift Model */, 167 | 0328715B1DC53C30002E4584 /* Swift Model --> Swift Source Code */, 168 | 03B52FD61DC3FD9D00910D1B /* command-line-interface.swift */, 169 | 03B0D0C41DAFCD7C00D20E31 /* main.swift */, 170 | ); 171 | path = json2swift; 172 | sourceTree = ""; 173 | }; 174 | 03B0D0D01DAFCD9C00D20E31 /* unit_tests */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 03B0D0D31DAFCD9C00D20E31 /* Info.plist */, 178 | 03B0D0E11DAFCDF700D20E31 /* json-attribute-merging-boolean-tests.swift */, 179 | 03B0D0E01DAFCDF700D20E31 /* json-attribute-merging-element-array-tests.swift */, 180 | 03B0D0E21DAFCDF700D20E31 /* json-attribute-merging-element-tests.swift */, 181 | 03237E021DC58A7B00ECCEF9 /* json-attribute-merging-empty-array-tests.swift */, 182 | 03B0D0E31DAFCDF700D20E31 /* json-attribute-merging-numeric-tests.swift */, 183 | 03B0D0E41DAFCDF700D20E31 /* json-attribute-merging-text-tests.swift */, 184 | 03720E451DB177D600BCFE72 /* json-attribute-merging-value-array-tests.swift */, 185 | 03A195821DAFEA7A00481B1F /* json-schema-inference-element-array-tests.swift */, 186 | 03B0D0E51DAFCDF700D20E31 /* json-schema-inference-element-tests.swift */, 187 | 03720E491DB18E3D00BCFE72 /* json-schema-inference-value-array-tests.swift */, 188 | 03720E471DB17FE400BCFE72 /* JSONType+JSONType.swift */, 189 | 032199E21DBC266900BF8272 /* name-translation-tests.swift */, 190 | 0392CC7A1DBA7E7D00289A67 /* swift-code-generation-custom-struct-tests.swift */, 191 | 03B89CA71DBAC00B007B9776 /* swift-code-generation-primitive-array-tests.swift */, 192 | 0392CC781DBA7AFB00289A67 /* swift-code-generation-primitive-value-tests.swift */, 193 | ); 194 | path = unit_tests; 195 | sourceTree = ""; 196 | }; 197 | /* End PBXGroup section */ 198 | 199 | /* Begin PBXNativeTarget section */ 200 | 03B0D0C01DAFCD7C00D20E31 /* json2swift */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = 03B0D0C81DAFCD7C00D20E31 /* Build configuration list for PBXNativeTarget "json2swift" */; 203 | buildPhases = ( 204 | 03B0D0BD1DAFCD7C00D20E31 /* Sources */, 205 | 03B0D0BE1DAFCD7C00D20E31 /* Frameworks */, 206 | 03B0D0BF1DAFCD7C00D20E31 /* CopyFiles */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | ); 212 | name = json2swift; 213 | productName = json2swift; 214 | productReference = 03B0D0C11DAFCD7C00D20E31 /* json2swift */; 215 | productType = "com.apple.product-type.tool"; 216 | }; 217 | 03B0D0CE1DAFCD9C00D20E31 /* unit_tests */ = { 218 | isa = PBXNativeTarget; 219 | buildConfigurationList = 03B0D0D41DAFCD9C00D20E31 /* Build configuration list for PBXNativeTarget "unit_tests" */; 220 | buildPhases = ( 221 | 03B0D0CB1DAFCD9C00D20E31 /* Sources */, 222 | 03B0D0CC1DAFCD9C00D20E31 /* Frameworks */, 223 | 03B0D0CD1DAFCD9C00D20E31 /* Resources */, 224 | ); 225 | buildRules = ( 226 | ); 227 | dependencies = ( 228 | ); 229 | name = unit_tests; 230 | productName = unit_tests; 231 | productReference = 03B0D0CF1DAFCD9C00D20E31 /* unit_tests.xctest */; 232 | productType = "com.apple.product-type.bundle.unit-test"; 233 | }; 234 | /* End PBXNativeTarget section */ 235 | 236 | /* Begin PBXProject section */ 237 | 03B0D0B91DAFCD7C00D20E31 /* Project object */ = { 238 | isa = PBXProject; 239 | attributes = { 240 | LastSwiftUpdateCheck = 0800; 241 | LastUpgradeCheck = 1000; 242 | ORGANIZATIONNAME = iJoshSmith; 243 | TargetAttributes = { 244 | 03B0D0C01DAFCD7C00D20E31 = { 245 | CreatedOnToolsVersion = 8.0; 246 | LastSwiftMigration = 1000; 247 | ProvisioningStyle = Automatic; 248 | }; 249 | 03B0D0CE1DAFCD9C00D20E31 = { 250 | CreatedOnToolsVersion = 8.0; 251 | LastSwiftMigration = 1000; 252 | ProvisioningStyle = Automatic; 253 | }; 254 | }; 255 | }; 256 | buildConfigurationList = 03B0D0BC1DAFCD7C00D20E31 /* Build configuration list for PBXProject "json2swift" */; 257 | compatibilityVersion = "Xcode 3.2"; 258 | developmentRegion = English; 259 | hasScannedForEncodings = 0; 260 | knownRegions = ( 261 | en, 262 | ); 263 | mainGroup = 03B0D0B81DAFCD7C00D20E31; 264 | productRefGroup = 03B0D0C21DAFCD7C00D20E31 /* products */; 265 | projectDirPath = ""; 266 | projectRoot = ""; 267 | targets = ( 268 | 03B0D0C01DAFCD7C00D20E31 /* json2swift */, 269 | 03B0D0CE1DAFCD9C00D20E31 /* unit_tests */, 270 | ); 271 | }; 272 | /* End PBXProject section */ 273 | 274 | /* Begin PBXResourcesBuildPhase section */ 275 | 03B0D0CD1DAFCD9C00D20E31 /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | /* End PBXResourcesBuildPhase section */ 283 | 284 | /* Begin PBXSourcesBuildPhase section */ 285 | 03B0D0BD1DAFCD7C00D20E31 /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 036BF2D61DB14CA50004C4FC /* swift-data-model.swift in Sources */, 290 | 03B52FD71DC3FD9D00910D1B /* command-line-interface.swift in Sources */, 291 | 03B0D0DA1DAFCDD000D20E31 /* json-attribute-merging.swift in Sources */, 292 | 03DCE0AA1DB086C10087787F /* json-helpers.swift in Sources */, 293 | 03B0D0C51DAFCD7C00D20E31 /* main.swift in Sources */, 294 | 03B0D0DC1DAFCDD000D20E31 /* json-schema-inference.swift in Sources */, 295 | 036BF2D31DB14C630004C4FC /* swift-code-generation.swift in Sources */, 296 | 03D815671DB83BDA00B88564 /* swift-code-templates.swift in Sources */, 297 | 03B0D0DB1DAFCDD000D20E31 /* json-data-model.swift in Sources */, 298 | 03524D651DC27A5A0002E0CD /* name-translation.swift in Sources */, 299 | 03C8303E1DC3B98500999D21 /* failable-initializer-translation.swift in Sources */, 300 | 032199E01DBC1B2200BF8272 /* schema-to-struct-translation.swift in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 03B0D0CB1DAFCD9C00D20E31 /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 03B89CA81DBAC00B007B9776 /* swift-code-generation-primitive-array-tests.swift in Sources */, 309 | 03B0D0E61DAFCDF700D20E31 /* json-attribute-merging-element-array-tests.swift in Sources */, 310 | 03B0D0EB1DAFCDF700D20E31 /* json-schema-inference-element-tests.swift in Sources */, 311 | 03720E4A1DB18E3D00BCFE72 /* json-schema-inference-value-array-tests.swift in Sources */, 312 | 03B0D0DD1DAFCDD900D20E31 /* json-attribute-merging.swift in Sources */, 313 | 03720E481DB17FE400BCFE72 /* JSONType+JSONType.swift in Sources */, 314 | 036BF2D71DB14CA50004C4FC /* swift-data-model.swift in Sources */, 315 | 03B0D0DF1DAFCDD900D20E31 /* json-schema-inference.swift in Sources */, 316 | 03B52FD81DC3FD9D00910D1B /* command-line-interface.swift in Sources */, 317 | 032199E11DBC1B2200BF8272 /* schema-to-struct-translation.swift in Sources */, 318 | 036BF2D41DB14C630004C4FC /* swift-code-generation.swift in Sources */, 319 | 03B0D0E91DAFCDF700D20E31 /* json-attribute-merging-numeric-tests.swift in Sources */, 320 | 032199E31DBC266900BF8272 /* name-translation-tests.swift in Sources */, 321 | 03D815681DB83BDA00B88564 /* swift-code-templates.swift in Sources */, 322 | 03DCE0AB1DB086C10087787F /* json-helpers.swift in Sources */, 323 | 03B0D0E71DAFCDF700D20E31 /* json-attribute-merging-boolean-tests.swift in Sources */, 324 | 03524D661DC27A5A0002E0CD /* name-translation.swift in Sources */, 325 | 0392CC791DBA7AFB00289A67 /* swift-code-generation-primitive-value-tests.swift in Sources */, 326 | 03B0D0EA1DAFCDF700D20E31 /* json-attribute-merging-text-tests.swift in Sources */, 327 | 03C8303F1DC3B98500999D21 /* failable-initializer-translation.swift in Sources */, 328 | 03B0D0DE1DAFCDD900D20E31 /* json-data-model.swift in Sources */, 329 | 03A195831DAFEA7A00481B1F /* json-schema-inference-element-array-tests.swift in Sources */, 330 | 03720E461DB177D600BCFE72 /* json-attribute-merging-value-array-tests.swift in Sources */, 331 | 03B0D0E81DAFCDF700D20E31 /* json-attribute-merging-element-tests.swift in Sources */, 332 | 03237E031DC58A7B00ECCEF9 /* json-attribute-merging-empty-array-tests.swift in Sources */, 333 | 0392CC7B1DBA7E7D00289A67 /* swift-code-generation-custom-struct-tests.swift in Sources */, 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | /* End PBXSourcesBuildPhase section */ 338 | 339 | /* Begin XCBuildConfiguration section */ 340 | 03B0D0C61DAFCD7C00D20E31 /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ALWAYS_SEARCH_USER_PATHS = NO; 344 | CLANG_ANALYZER_NONNULL = YES; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_COMMA = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 356 | CLANG_WARN_EMPTY_BODY = YES; 357 | CLANG_WARN_ENUM_CONVERSION = YES; 358 | CLANG_WARN_INFINITE_RECURSION = YES; 359 | CLANG_WARN_INT_CONVERSION = YES; 360 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 364 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 365 | CLANG_WARN_STRICT_PROTOTYPES = YES; 366 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 367 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | CODE_SIGN_IDENTITY = "-"; 371 | COPY_PHASE_STRIP = NO; 372 | DEBUG_INFORMATION_FORMAT = dwarf; 373 | ENABLE_STRICT_OBJC_MSGSEND = YES; 374 | ENABLE_TESTABILITY = YES; 375 | GCC_C_LANGUAGE_STANDARD = gnu99; 376 | GCC_DYNAMIC_NO_PIC = NO; 377 | GCC_NO_COMMON_BLOCKS = YES; 378 | GCC_OPTIMIZATION_LEVEL = 0; 379 | GCC_PREPROCESSOR_DEFINITIONS = ( 380 | "DEBUG=1", 381 | "$(inherited)", 382 | ); 383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 384 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 385 | GCC_WARN_UNDECLARED_SELECTOR = YES; 386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 387 | GCC_WARN_UNUSED_FUNCTION = YES; 388 | GCC_WARN_UNUSED_VARIABLE = YES; 389 | MACOSX_DEPLOYMENT_TARGET = 10.11; 390 | MTL_ENABLE_DEBUG_INFO = YES; 391 | ONLY_ACTIVE_ARCH = YES; 392 | SDKROOT = macosx; 393 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 394 | }; 395 | name = Debug; 396 | }; 397 | 03B0D0C71DAFCD7C00D20E31 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | ALWAYS_SEARCH_USER_PATHS = NO; 401 | CLANG_ANALYZER_NONNULL = YES; 402 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 403 | CLANG_CXX_LIBRARY = "libc++"; 404 | CLANG_ENABLE_MODULES = YES; 405 | CLANG_ENABLE_OBJC_ARC = YES; 406 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_COMMA = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 413 | CLANG_WARN_EMPTY_BODY = YES; 414 | CLANG_WARN_ENUM_CONVERSION = YES; 415 | CLANG_WARN_INFINITE_RECURSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 422 | CLANG_WARN_STRICT_PROTOTYPES = YES; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 425 | CLANG_WARN_UNREACHABLE_CODE = YES; 426 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 427 | CODE_SIGN_IDENTITY = "-"; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 430 | ENABLE_NS_ASSERTIONS = NO; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | GCC_C_LANGUAGE_STANDARD = gnu99; 433 | GCC_NO_COMMON_BLOCKS = YES; 434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 436 | GCC_WARN_UNDECLARED_SELECTOR = YES; 437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 438 | GCC_WARN_UNUSED_FUNCTION = YES; 439 | GCC_WARN_UNUSED_VARIABLE = YES; 440 | MACOSX_DEPLOYMENT_TARGET = 10.11; 441 | MTL_ENABLE_DEBUG_INFO = NO; 442 | ONLY_ACTIVE_ARCH = NO; 443 | SDKROOT = macosx; 444 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 445 | }; 446 | name = Release; 447 | }; 448 | 03B0D0C91DAFCD7C00D20E31 /* Debug */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 454 | SWIFT_VERSION = 4.2; 455 | }; 456 | name = Debug; 457 | }; 458 | 03B0D0CA1DAFCD7C00D20E31 /* Release */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 463 | SWIFT_VERSION = 4.2; 464 | }; 465 | name = Release; 466 | }; 467 | 03B0D0D51DAFCD9C00D20E31 /* Debug */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | COMBINE_HIDPI_IMAGES = YES; 471 | INFOPLIST_FILE = unit_tests/Info.plist; 472 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 473 | PRODUCT_BUNDLE_IDENTIFIER = "iJoshSmith.unit-tests"; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 476 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 477 | SWIFT_VERSION = 4.2; 478 | }; 479 | name = Debug; 480 | }; 481 | 03B0D0D61DAFCD9C00D20E31 /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | COMBINE_HIDPI_IMAGES = YES; 485 | INFOPLIST_FILE = unit_tests/Info.plist; 486 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 487 | PRODUCT_BUNDLE_IDENTIFIER = "iJoshSmith.unit-tests"; 488 | PRODUCT_NAME = "$(TARGET_NAME)"; 489 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 490 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 491 | SWIFT_VERSION = 4.2; 492 | }; 493 | name = Release; 494 | }; 495 | /* End XCBuildConfiguration section */ 496 | 497 | /* Begin XCConfigurationList section */ 498 | 03B0D0BC1DAFCD7C00D20E31 /* Build configuration list for PBXProject "json2swift" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | 03B0D0C61DAFCD7C00D20E31 /* Debug */, 502 | 03B0D0C71DAFCD7C00D20E31 /* Release */, 503 | ); 504 | defaultConfigurationIsVisible = 0; 505 | defaultConfigurationName = Release; 506 | }; 507 | 03B0D0C81DAFCD7C00D20E31 /* Build configuration list for PBXNativeTarget "json2swift" */ = { 508 | isa = XCConfigurationList; 509 | buildConfigurations = ( 510 | 03B0D0C91DAFCD7C00D20E31 /* Debug */, 511 | 03B0D0CA1DAFCD7C00D20E31 /* Release */, 512 | ); 513 | defaultConfigurationIsVisible = 0; 514 | defaultConfigurationName = Release; 515 | }; 516 | 03B0D0D41DAFCD9C00D20E31 /* Build configuration list for PBXNativeTarget "unit_tests" */ = { 517 | isa = XCConfigurationList; 518 | buildConfigurations = ( 519 | 03B0D0D51DAFCD9C00D20E31 /* Debug */, 520 | 03B0D0D61DAFCD9C00D20E31 /* Release */, 521 | ); 522 | defaultConfigurationIsVisible = 0; 523 | defaultConfigurationName = Release; 524 | }; 525 | /* End XCConfigurationList section */ 526 | }; 527 | rootObject = 03B0D0B91DAFCD7C00D20E31 /* Project object */; 528 | } 529 | -------------------------------------------------------------------------------- /json2swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /json2swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /json2swift/command-line-interface.swift: -------------------------------------------------------------------------------- 1 | // 2 | // command-line-interface.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/28/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias ErrorMessage = String 12 | 13 | func run(with arguments: [String]) -> ErrorMessage? { 14 | guard arguments.isEmpty == false else { return "Please provide a JSON file path or directory path." } 15 | 16 | let path = (arguments[0] as NSString).resolvingSymlinksInPath 17 | 18 | var isDirectory: ObjCBool = false 19 | guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) else { return "No such file or directory exists." } 20 | 21 | let jsonFilePaths: [String] 22 | if isDirectory.boolValue { 23 | guard let filePaths = findJSONFilePaths(in: path) else { return "Unable to read contents of directory." } 24 | guard filePaths.isEmpty == false else { return "The directory does not contain any JSON files." } 25 | jsonFilePaths = filePaths 26 | } 27 | else { 28 | jsonFilePaths = [path] 29 | } 30 | 31 | let jsonUtilitiesFilePath: String? = jsonFilePaths.count > 1 32 | ? (path as NSString).appendingPathComponent("JSONUtilities.swift") 33 | : nil 34 | 35 | let shouldIncludeUtils = jsonUtilitiesFilePath == nil 36 | let rootType = arguments.count >= 2 ? arguments[1] : "root-type" 37 | for jsonFilePath in jsonFilePaths { 38 | if let errorMessage = generateSwiftFileBasedOnJSON(inFile: jsonFilePath, includeJSONUtilities: shouldIncludeUtils, rootTypeName: rootType) { 39 | return errorMessage 40 | } 41 | } 42 | 43 | if let jsonUtilitiesFilePath = jsonUtilitiesFilePath { 44 | guard writeJSONUtilitiesFile(to: jsonUtilitiesFilePath) else { return "Unable to write JSON utilities file to \(jsonUtilitiesFilePath)" } 45 | } 46 | 47 | return nil 48 | } 49 | 50 | 51 | // MARK: - Finding JSON files in directory 52 | private func findJSONFilePaths(in directory: String) -> [String]? { 53 | guard let jsonFileNames = findJSONFileNames(in: directory) else { return nil } 54 | return resolveAbsolutePaths(for: jsonFileNames, inDirectory: directory) 55 | } 56 | 57 | private func findJSONFileNames(in directory: String) -> [String]? { 58 | let isJSONFile: (String) -> Bool = { $0.lowercased().hasSuffix(".json") } 59 | do { return try FileManager.default.contentsOfDirectory(atPath: directory).filter(isJSONFile) } 60 | catch { return nil } 61 | } 62 | 63 | private func resolveAbsolutePaths(for jsonFileNames: [String], inDirectory directory: String) -> [String] { 64 | return jsonFileNames.map { (directory as NSString).appendingPathComponent($0) } 65 | } 66 | 67 | 68 | // MARK: - Generating Swift file based on JSON 69 | private func generateSwiftFileBasedOnJSON(inFile jsonFilePath: String, includeJSONUtilities: Bool, rootTypeName: String) -> ErrorMessage? { 70 | let url = URL(fileURLWithPath: jsonFilePath) 71 | let data: Data 72 | do { data = try Data(contentsOf: url) } 73 | catch { return "Unable to read file: \(jsonFilePath)" } 74 | 75 | let jsonObject: Any 76 | do { jsonObject = try JSONSerialization.jsonObject(with: data, options: []) } 77 | catch { return "File does not contain valid JSON: \(jsonFilePath)" } 78 | 79 | let jsonSchema: JSONElementSchema 80 | if let jsonElement = jsonObject as? JSONElement { jsonSchema = JSONElementSchema.inferred(from: jsonElement, named: rootTypeName) } 81 | else if let jsonArray = jsonObject as? [JSONElement] { jsonSchema = JSONElementSchema.inferred(from: jsonArray, named: rootTypeName) } 82 | else { return "Unsupported JSON format: must be a dictionary or array of dictionaries." } 83 | 84 | let swiftStruct = SwiftStruct.create(from: jsonSchema) 85 | let swiftCode = includeJSONUtilities 86 | ? SwiftCodeGenerator.generateCodeWithJSONUtilities(for: swiftStruct) 87 | : SwiftCodeGenerator.generateCode(for: swiftStruct) 88 | 89 | let swiftFilePath = (jsonFilePath as NSString).deletingPathExtension + ".swift" 90 | guard write(swiftCode: swiftCode, toFile: swiftFilePath) else { return "Unable to write to file: \(swiftFilePath)" } 91 | 92 | return nil 93 | } 94 | 95 | private func writeJSONUtilitiesFile(to filePath: String) -> Bool { 96 | let jsonUtilitiesCode = SwiftCodeGenerator.generateJSONUtilities() 97 | return write(swiftCode: jsonUtilitiesCode, toFile: filePath) 98 | } 99 | 100 | private func write(swiftCode: String, toFile filePath: String) -> Bool { 101 | do { 102 | try swiftCode.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8) 103 | return true 104 | } 105 | catch { 106 | return false 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /json2swift/failable-initializer-translation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // failable-initializer-translation.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/28/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | // MARK: - JSONElementSchema --> SwiftFailableInitializer 10 | internal extension SwiftFailableInitializer { 11 | static func create(forStructBasedOn jsonElementSchema: JSONElementSchema) -> SwiftFailableInitializer { 12 | let attributeMap = jsonElementSchema.attributes 13 | let allAttributeNames = Set(attributeMap.keys) 14 | let requiredAttributeNames = allAttributeNames.filter { attributeMap[$0]!.isRequired } 15 | let optionalAttributeNames = allAttributeNames.subtracting(requiredAttributeNames) 16 | let requiredTransformations: [TransformationFromJSON] = requiredAttributeNames.map { 17 | TransformationFromJSON.create(forAttributeNamed: $0, inAttributeMap: attributeMap) 18 | } 19 | let optionalTransformations: [TransformationFromJSON] = optionalAttributeNames.map { 20 | TransformationFromJSON.create(forAttributeNamed: $0, inAttributeMap: attributeMap) 21 | } 22 | return SwiftFailableInitializer(requiredTransformations: requiredTransformations, 23 | optionalTransformations: optionalTransformations) 24 | } 25 | } 26 | 27 | // MARK: - JSON attribute --> TransformationFromJSON 28 | fileprivate extension TransformationFromJSON { 29 | static func create(forAttributeNamed attributeName: String, inAttributeMap attributeMap: JSONAttributeMap) -> TransformationFromJSON { 30 | let propertyName = attributeName.toSwiftPropertyName() 31 | let jsonType = attributeMap[attributeName]! 32 | switch jsonType { 33 | case let .element(_, schema): 34 | let swiftStruct = SwiftStruct.create(from: schema) 35 | return TransformationFromJSON.toCustomStruct(attributeName: attributeName, 36 | propertyName: propertyName, 37 | type: swiftStruct) 38 | 39 | case let .elementArray(_, elementSchema, hasNullableElements): 40 | let swiftStruct = SwiftStruct.create(from: elementSchema) 41 | return TransformationFromJSON.toCustomStructArray(attributeName: attributeName, 42 | propertyName: propertyName, 43 | elementType: swiftStruct, 44 | hasOptionalElements: hasNullableElements) 45 | 46 | case let .valueArray(_, valueType): 47 | let elementType = SwiftPrimitiveValueType.create(from: valueType) 48 | let hasOptionalElements = valueType.isRequired == false 49 | return TransformationFromJSON.toPrimitiveValueArray(attributeName: attributeName, 50 | propertyName: propertyName, 51 | elementType: elementType, 52 | hasOptionalElements: hasOptionalElements) 53 | 54 | default: 55 | let valueType = SwiftPrimitiveValueType.create(from: jsonType) 56 | return TransformationFromJSON.toPrimitiveValue(attributeName: attributeName, 57 | propertyName: propertyName, 58 | type: valueType) 59 | } 60 | } 61 | } 62 | 63 | // MARK: - JSONType --> SwiftPrimitiveValueType 64 | fileprivate extension SwiftPrimitiveValueType { 65 | static func create(from jsonType: JSONType) -> SwiftPrimitiveValueType { 66 | switch jsonType { 67 | case .anything, 68 | .element, 69 | .elementArray, 70 | .nullable, 71 | .valueArray: return .any 72 | case .emptyArray: return .emptyArray 73 | case .number(_, let isFloatingPoint): return isFloatingPoint ? .double : .int 74 | case .date(_, let format): return .date(format: format) 75 | case .url: return .url 76 | case .string: return .string 77 | case .bool: return .bool 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /json2swift/json-attribute-merging.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/11/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | // MARK: - JSONElementSchema extension 10 | extension JSONElementSchema { 11 | static func inferredByMergingAttributes(of schemas: [JSONElementSchema], named name: String) -> JSONElementSchema { 12 | switch schemas.count { 13 | case 0: return JSONElementSchema(name: name) 14 | case 1: return schemas[0] 15 | default: return schemas.dropFirst().reduce(schemas[0], merge(schema:withSchema:)) 16 | } 17 | } 18 | 19 | private static func merge(schema: JSONElementSchema, withSchema otherSchema: JSONElementSchema) -> JSONElementSchema { 20 | return schema.merged(with: otherSchema) 21 | } 22 | 23 | fileprivate func merged(with schema: JSONElementSchema) -> JSONElementSchema { 24 | let mergedAttributes = JSONType.merge(attributes: attributes, with: schema.attributes) 25 | return JSONElementSchema(name: name, attributes: mergedAttributes) 26 | } 27 | } 28 | 29 | // MARK: - JSONType extension 30 | extension JSONType { 31 | fileprivate static func merge(attributes: JSONAttributeMap, with otherAttributes: JSONAttributeMap) -> JSONAttributeMap { 32 | let attributeNames = Set(Array(attributes.keys) + Array(otherAttributes.keys)) 33 | let mergedAttributes = attributeNames.map { name -> (String, JSONType) in 34 | let type = attributes[name] ?? .nullable 35 | let otherType = otherAttributes[name] ?? .nullable 36 | let compatibleType = type.findCompatibleType(with: otherType) 37 | return (name, compatibleType) 38 | } 39 | return JSONAttributeMap(entries: mergedAttributes) 40 | } 41 | 42 | internal func findCompatibleType(with type: JSONType) -> JSONType { 43 | return JSONType.compatible(with: self, and: type) 44 | } 45 | 46 | private static func compatible(with type1: JSONType, and type2: JSONType) -> JSONType { 47 | switch (type1, type2) { 48 | // Element 49 | case let (.element(r1, s1), .element(r2, s2)): return .element(isRequired: r1 && r2, schema: s1.merged(with: s2)) 50 | case let (.element(_, s), .nullable): return .element(isRequired: false, schema: s) 51 | case let (.nullable, .element(_, s)): return .element(isRequired: false, schema: s) 52 | 53 | // Element Array 54 | case let (.elementArray(r1, s1, n1), .elementArray(r2, s2, n2)): return .elementArray(isRequired: r1 && r2, elementSchema: s1.merged(with: s2), hasNullableElements: n1 || n2) 55 | case let (.elementArray(_, s, n), .nullable): return .elementArray(isRequired: false, elementSchema: s, hasNullableElements: n) 56 | case let (.nullable, .elementArray(_, s, n)): return .elementArray(isRequired: false, elementSchema: s, hasNullableElements: n) 57 | 58 | // Value Array 59 | case let (.valueArray(r1, t1), .valueArray(r2, t2)): return .valueArray(isRequired: r1 && r2, valueType: t1.findCompatibleType(with: t2)) 60 | case let (.valueArray(_, t), .nullable): return .valueArray(isRequired: false, valueType: t) 61 | case let (.nullable, .valueArray(_, t)): return .valueArray(isRequired: false, valueType: t) 62 | 63 | // Numeric 64 | case let (.number(r1, fp1), .number(r2, fp2)): return .number(isRequired: r1 && r2, isFloatingPoint: fp1 || fp2) 65 | case let (.number(_, fp), .nullable): return .number(isRequired: false, isFloatingPoint: fp) 66 | case let (.nullable, .number(_, fp)): return .number(isRequired: false, isFloatingPoint: fp) 67 | 68 | // Text 69 | case let (.date(r1, f), .date(r2, _)): return .date( isRequired: r1 && r2, format: f) 70 | case let (.date(r1, _), .url(r2)): return .string(isRequired: r1 && r2) 71 | case let (.date(r1, f), .string(r2)): return .date( isRequired: r1 && r2, format: f) 72 | case let (.date(_, f), .nullable): return .date( isRequired: false, format: f) 73 | case let (.nullable, .date(_, f)): return .date( isRequired: false, format: f) 74 | case let (.url(r1), .date(r2, _)): return .string(isRequired: r1 && r2) 75 | case let (.url(r1), .url(r2)): return .url( isRequired: r1 && r2) 76 | case let (.url(r1), .string(r2)): return .string(isRequired: r1 && r2) 77 | case (.url, .nullable): return .url( isRequired: false) 78 | case (.nullable, .url): return .url( isRequired: false) 79 | case let (.string(r1), .date(r2, f)): return .date( isRequired: r1 && r2, format: f) 80 | case let (.string(r1), .url(r2)): return .string(isRequired: r1 && r2) 81 | case let (.string(r1), .string(r2)): return .string(isRequired: r1 && r2) 82 | case (.string, .nullable): return .string(isRequired: false) 83 | case (.nullable, .string): return .string(isRequired: false) 84 | 85 | // Boolean 86 | case let (.bool(r1), .bool(r2)): return .bool(isRequired: r1 && r2) 87 | case (.bool, .nullable): return .bool(isRequired: false) 88 | case (.nullable, .bool): return .bool(isRequired: false) 89 | 90 | // Nullable (e.g. both attribute values were null) 91 | case (.nullable, .nullable): return .nullable 92 | 93 | // Empty array 94 | case (.emptyArray, .emptyArray): return .emptyArray 95 | case (.elementArray, .emptyArray): return type1 96 | case (.valueArray, .emptyArray): return type1 97 | case (.emptyArray, .elementArray): return type2 98 | case (.emptyArray, .valueArray): return type2 99 | 100 | // Unrelated types 101 | default: return .anything 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /json2swift/json-data-model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-data-model.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/10/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | /// The prefix to use on a sample JSON attribute whose value is a date string. 10 | /// This provides a hint to the JSON analyzer that the corresponding property is a Date. 11 | /// For example, use "DATE_FORMAT=MM/dd/yyyy" for an attribute whose values are like "12/25/2016". 12 | let dateFormatPrefix = "DATE_FORMAT=" 13 | 14 | typealias JSONValue = Any 15 | typealias JSONElement = [String: JSONValue] 16 | 17 | // MARK: - JSONType 18 | indirect enum JSONType { 19 | case element( isRequired: Bool, schema: JSONElementSchema) 20 | case elementArray(isRequired: Bool, elementSchema: JSONElementSchema, hasNullableElements: Bool) 21 | case valueArray( isRequired: Bool, valueType: JSONType) 22 | case number( isRequired: Bool, isFloatingPoint: Bool) 23 | case date( isRequired: Bool, format: String) 24 | case url( isRequired: Bool) 25 | case string( isRequired: Bool) 26 | case bool( isRequired: Bool) 27 | case nullable // For an attribute that is missing or has a null value. 28 | case anything // For an attribute with multiple values of unrelated types. 29 | case emptyArray // For an attribute that contains an empty array. 30 | } 31 | 32 | extension JSONType { 33 | var isRequired: Bool { 34 | switch self { 35 | case let .element (isRequired, _): return isRequired 36 | case let .elementArray(isRequired, _, _): return isRequired 37 | case let .valueArray (isRequired, _): return isRequired 38 | case let .number (isRequired, _): return isRequired 39 | case let .date (isRequired, _): return isRequired 40 | case let .url (isRequired): return isRequired 41 | case let .string (isRequired): return isRequired 42 | case let .bool (isRequired): return isRequired 43 | case .nullable, .anything: return false 44 | case .emptyArray: return true 45 | } 46 | } 47 | 48 | var jsonElementSchema: JSONElementSchema? { 49 | switch self { 50 | case let .element(_, schema): return schema 51 | case let .elementArray(_, elementSchema, _): return elementSchema 52 | default: return nil 53 | } 54 | } 55 | } 56 | 57 | extension JSONType: Equatable { 58 | static func ==(lhs: JSONType, rhs: JSONType) -> Bool { 59 | switch (lhs, rhs) { 60 | case let (.element(r1, a), .element(r2, b)): return r1 == r2 && a == b 61 | case let (.elementArray(r1, a, n1), .elementArray(r2, b, n2)): return r1 == r2 && a == b && n1 == n2 62 | case let (.valueArray(r1, a), .valueArray(r2, b)): return r1 == r2 && a == b 63 | case let (.number(r1, a), .number(r2, b)): return r1 == r2 && a == b 64 | case let (.date(r1, a), .date(r2, b)): return r1 == r2 && a == b 65 | case let (.url(r1), .url(r2)): return r1 == r2 66 | case let (.string(r1), .string(r2)): return r1 == r2 67 | case let (.bool(r1), .bool(r2)): return r1 == r2 68 | case (.nullable, .nullable): return true 69 | case (.anything, .anything): return true 70 | case (.emptyArray, .emptyArray): return true 71 | default: return false 72 | } 73 | } 74 | } 75 | 76 | // MARK: - JSONElementSchema 77 | typealias JSONAttributeMap = [String: JSONType] 78 | struct JSONElementSchema { 79 | let name: String 80 | let attributes: JSONAttributeMap 81 | 82 | init(name: String, attributes: JSONAttributeMap = [:]) { 83 | self.name = name 84 | self.attributes = attributes 85 | } 86 | } 87 | 88 | extension JSONElementSchema: Equatable {} 89 | func ==(lhs: JSONElementSchema, rhs: JSONElementSchema) -> Bool { 90 | return lhs.name == rhs.name && lhs.attributes == rhs.attributes 91 | } 92 | -------------------------------------------------------------------------------- /json2swift/json-helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-helpers.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/13/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | extension Dictionary { 10 | /// Fills a new dictionary with key-value pairs. 11 | init(entries: [(Key, Value)]) { 12 | self.init(minimumCapacity: entries.count) 13 | entries.forEach { self[$0] = $1 } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /json2swift/json-schema-inference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-schema-inference.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/10/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - JSONElementSchema extension 12 | extension JSONElementSchema { 13 | static func inferred(from jsonElementArray: [JSONElement], named name: String) -> JSONElementSchema { 14 | let jsonElement = [name: jsonElementArray] 15 | let schema = JSONElementSchema.inferred(from: jsonElement, named: name) 16 | let (_, attributeType) = schema.attributes.first! 17 | switch attributeType { 18 | case .elementArray(_, let elementSchema, _): return elementSchema 19 | case .emptyArray: return JSONElementSchema(name: name) 20 | default: abort() 21 | } 22 | } 23 | 24 | static func inferred(from jsonElement: JSONElement, named name: String) -> JSONElementSchema { 25 | let attributes = createAttributeMap(for: jsonElement) 26 | return JSONElementSchema(name: name, attributes: attributes) 27 | } 28 | 29 | private static func createAttributeMap(for jsonElement: JSONElement) -> JSONAttributeMap { 30 | let attributes = jsonElement.map { (name, value) -> (String, JSONType) in 31 | let type = JSONType.inferType(of: value, named: name) 32 | return (name, type) 33 | } 34 | return JSONAttributeMap(entries: attributes) 35 | } 36 | } 37 | 38 | // MARK: - JSONType extension 39 | fileprivate extension JSONType { 40 | static func inferType(of value: Any, named name: String) -> JSONType { 41 | switch value { 42 | case let element as JSONElement: return inferType(of: element, named: name) 43 | case let array as [Any]: return inferType(of: array, named: name) 44 | case let nsValue as NSValue: return inferType(of: nsValue) 45 | case let string as String: return inferType(of: string) 46 | case is NSNull: return .nullable 47 | default: 48 | assertionFailure("Unexpected type of value in JSON: \(value)") 49 | abort() 50 | } 51 | } 52 | 53 | private static func inferType(of element: JSONElement, named name: String) -> JSONType { 54 | let schema = JSONElementSchema.inferred(from: element, named: name) 55 | return .element(isRequired: true, schema: schema) 56 | } 57 | 58 | private static func inferType(of array: [Any], named name: String) -> JSONType { 59 | let arrayWithoutNulls = array.filter { $0 is NSNull == false } 60 | if let elements = arrayWithoutNulls as? [JSONElement] { 61 | let hasNullableElements = arrayWithoutNulls.count < array.count 62 | return inferType(ofElementArray: elements, named: name, hasNullableElements: hasNullableElements) 63 | } 64 | else { 65 | return inferType(ofValueArray: array, named: name) 66 | } 67 | } 68 | 69 | private static func inferType(ofElementArray elementArray: [JSONElement], named name: String, hasNullableElements: Bool) -> JSONType { 70 | if elementArray.isEmpty { return .emptyArray } 71 | let schemas = elementArray.map { jsonElement in JSONElementSchema.inferred(from: jsonElement, named: name) } 72 | let mergedSchema = JSONElementSchema.inferredByMergingAttributes(of: schemas, named: name) 73 | return .elementArray(isRequired: true, elementSchema: mergedSchema, hasNullableElements: hasNullableElements) 74 | } 75 | 76 | private static func inferType(ofValueArray valueArray: [JSONValue], named name: String) -> JSONType { 77 | if valueArray.isEmpty { return .emptyArray } 78 | let valueType = inferValueType(of: valueArray, named: name) 79 | return .valueArray(isRequired: true, valueType: valueType) 80 | } 81 | 82 | private static func inferValueType(of values: [JSONValue], named name: String) -> JSONType { 83 | let types = values.map { inferType(of: $0, named: name) } 84 | switch types.count { 85 | case 0: abort() // Should never introspect an empty array. 86 | case 1: return types[0] 87 | default: return types.dropFirst().reduce(types[0]) { $0.findCompatibleType(with: $1) } 88 | } 89 | } 90 | 91 | private static func inferType(of nsValue: NSValue) -> JSONType { 92 | if nsValue.isBoolType { 93 | return .bool(isRequired: true) 94 | } 95 | else { 96 | return .number(isRequired: true, isFloatingPoint: nsValue.isFloatType) 97 | } 98 | } 99 | 100 | private static func inferType(of string: String) -> JSONType { 101 | if let dateFormat = string.extractDateFormat() { 102 | return .date(isRequired: true, format: dateFormat) 103 | } 104 | else if string.isWebAddress { 105 | return .url(isRequired: true) 106 | } 107 | else { 108 | return .string(isRequired: true) 109 | } 110 | } 111 | } 112 | 113 | // MARK: - String extension 114 | fileprivate extension String { 115 | func extractDateFormat() -> String? { 116 | let trimmed = trimmingCharacters(in: CharacterSet.whitespaces) 117 | return String.extractDateFormat(from: trimmed) 118 | } 119 | 120 | private static func extractDateFormat(from string: String) -> String? { 121 | guard let prefixRange = string.range(of: dateFormatPrefix) else { return nil } 122 | guard prefixRange.lowerBound == string.startIndex else { return nil } 123 | return String(string[prefixRange.upperBound...]) 124 | } 125 | 126 | var isWebAddress: Bool { 127 | guard let url = URL(string: self) else { return false } 128 | return url.scheme != nil && url.host != nil 129 | } 130 | } 131 | 132 | // MARK: - NSValue extension 133 | fileprivate extension NSValue { 134 | var isBoolType: Bool { return CFNumberGetType((self as! CFNumber)) == CFNumberType.charType } 135 | var isFloatType: Bool { return CFNumberIsFloatType((self as! CFNumber)) } 136 | } 137 | -------------------------------------------------------------------------------- /json2swift/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/13/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /* 12 | * This tool and its documentation are hosted at https://github.com/ijoshsmith/json2swift 13 | */ 14 | 15 | // The first argument is the executable's file path, which is irrelevant. 16 | let arguments = Array(CommandLine.arguments.dropFirst()) 17 | if let errorMessage = run(with: arguments) { 18 | print("Error: \(errorMessage)") 19 | exit(1) 20 | } 21 | else { 22 | print("Success: Created Swift data model(s)") 23 | exit(0) 24 | } 25 | -------------------------------------------------------------------------------- /json2swift/name-translation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // name-translation.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/27/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal extension String { 12 | func toSwiftStructName() -> String { 13 | let name = capitalizedWithoutInvalidChars.prefixedWithUnderscoreIfNecessary 14 | return name.isEmpty ? "DefaultStructName" : name 15 | } 16 | 17 | func toSwiftPropertyName() -> String { 18 | let name = capitalizedWithoutInvalidChars.lowercasedFirstCharacter.prefixedWithUnderscoreIfNecessary 19 | return name.isEmpty ? "defaultPropertyName" : name 20 | } 21 | 22 | private var capitalizedWithoutInvalidChars: String { 23 | let trimmed = trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 24 | let parts = trimmed.components(separatedBy: invalidSwiftNameCharacters) 25 | let capitalizedParts = parts.map { $0.uppercasedFirstCharacter } 26 | return capitalizedParts.joined() 27 | } 28 | 29 | private var prefixedWithUnderscoreIfNecessary: String { 30 | return isSwiftKeyword || startsWithNumber 31 | ? "_" + self 32 | : self 33 | } 34 | 35 | private var uppercasedFirstCharacter: String { 36 | return modifyFirstCharacter(byApplying: { String($0).uppercased() }) 37 | } 38 | 39 | private var lowercasedFirstCharacter: String { 40 | return modifyFirstCharacter(byApplying: { String($0).lowercased() }) 41 | } 42 | 43 | private func modifyFirstCharacter(byApplying characterTransform: (Character) -> String) -> String { 44 | guard isEmpty == false else { return self } 45 | let firstChar = self.first! 46 | let modifiedFirstChar = characterTransform(firstChar) 47 | let rangeOfFirstRange = Range(uncheckedBounds: (lower: startIndex, upper: index(after: startIndex))) 48 | return replacingCharacters(in: rangeOfFirstRange, with: modifiedFirstChar) 49 | } 50 | 51 | private var isSwiftKeyword: Bool { 52 | return swiftKeywords.contains(self) 53 | } 54 | 55 | private var startsWithNumber: Bool { 56 | guard let digitRange = rangeOfCharacter(from: CharacterSet.decimalDigits) else { return false } 57 | return digitRange.lowerBound == startIndex 58 | } 59 | } 60 | 61 | private let invalidSwiftNameCharacters = CharacterSet.alphanumerics.inverted 62 | private let swiftKeywords: Set = [ 63 | "Any", 64 | "as", 65 | "associatedtype", 66 | "break", 67 | "case", 68 | "catch", 69 | "class", 70 | "continue", 71 | "default", 72 | "defer", 73 | "deinit", 74 | "do", 75 | "else", 76 | "enum", 77 | "extension", 78 | "fallthrough", 79 | "false", 80 | "fileprivate", 81 | "for", 82 | "func", 83 | "guard", 84 | "if", 85 | "import", 86 | "in", 87 | "init", 88 | "inout", 89 | "internal", 90 | "is", 91 | "let", 92 | "nil", 93 | "open", 94 | "operator", 95 | "private", 96 | "protocol", 97 | "public", 98 | "repeat", 99 | "rethrows", 100 | "return", 101 | "Self", 102 | "self", 103 | "static", 104 | "struct", 105 | "subscript", 106 | "super", 107 | "switch", 108 | "throw", 109 | "throws", 110 | "true", 111 | "try", 112 | "typealias", 113 | "var", 114 | "where", 115 | "while" 116 | ] 117 | -------------------------------------------------------------------------------- /json2swift/schema-to-struct-translation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // schema-to-struct-translation.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/22/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | // MARK: - JSONElementSchema --> SwiftStruct 10 | internal extension SwiftStruct { 11 | static func create(from jsonElementSchema: JSONElementSchema) -> SwiftStruct { 12 | let name = jsonElementSchema.name.toSwiftStructName() 13 | let properties = SwiftProperty.createProperties(forStructBasedOn: jsonElementSchema) 14 | let parameters = SwiftParameter.createParameters(for: properties) 15 | let initializer = SwiftInitializer(parameters: parameters) 16 | let failableInitializer = SwiftFailableInitializer.create(forStructBasedOn: jsonElementSchema) 17 | let nestedStructs = createNestedStructs(forElementsIn: jsonElementSchema) 18 | return SwiftStruct(name: name, 19 | properties: properties, 20 | initializer: initializer, 21 | failableInitializer: failableInitializer, 22 | nestedStructs: nestedStructs) 23 | } 24 | 25 | private static func createNestedStructs(forElementsIn jsonElementSchema: JSONElementSchema) -> [SwiftStruct] { 26 | return jsonElementSchema.attributes.values.compactMap(SwiftStruct.tryToCreate(fromJSONType:)) 27 | } 28 | 29 | private static func tryToCreate(fromJSONType jsonType: JSONType) -> SwiftStruct? { 30 | if let schema = jsonType.jsonElementSchema { 31 | return SwiftStruct.create(from: schema) 32 | } 33 | else { 34 | return nil 35 | } 36 | } 37 | } 38 | 39 | // MARK: - JSONElementSchema --> SwiftProperty 40 | fileprivate extension SwiftProperty { 41 | static func createProperties(forStructBasedOn jsonElementSchema: JSONElementSchema) -> [SwiftProperty] { 42 | return jsonElementSchema.attributes.map { (name, type) in 43 | createProperty(basedOnJSONAttribute: name, and: type) 44 | } 45 | } 46 | 47 | private static func createProperty(basedOnJSONAttribute attributeName: String, and attributeType: JSONType) -> SwiftProperty { 48 | let propertyName = attributeName.toSwiftPropertyName() 49 | let propertyType = SwiftType.createType(from: attributeType) 50 | return SwiftProperty(name: propertyName, type: propertyType) 51 | } 52 | } 53 | 54 | // MARK: - SwiftProperty --> SwiftParameter 55 | fileprivate extension SwiftParameter { 56 | static func createParameters(for properties: [SwiftProperty]) -> [SwiftParameter] { 57 | return properties.map { SwiftParameter(name: $0.name, type: $0.type) } 58 | } 59 | } 60 | 61 | // MARK: - JSONType --> SwiftType 62 | fileprivate extension SwiftType { 63 | static func createType(from jsonType: JSONType) -> SwiftType { 64 | let typeName = jsonType.swiftTypeName 65 | let isOptional = jsonType.isRequired == false 66 | return SwiftType(name: typeName, isOptional: isOptional) 67 | } 68 | } 69 | 70 | // MARK: - JSONType --> Swift type name 71 | fileprivate extension JSONType { 72 | var swiftTypeName: String { 73 | switch self { 74 | case let .element(_, schema): return schema.name.toSwiftStructName() 75 | case let .elementArray(_, elementSchema, hasNullableElements): return JSONType.nameForArray(of: elementSchema, hasNullableElements) 76 | case let .valueArray(_, valueType): return JSONType.nameForArray(of: valueType) 77 | case let .number(_, isFloatingPoint): return isFloatingPoint ? "Double" : "Int" 78 | case .date: return "Date" 79 | case .url: return "URL" 80 | case .string: return "String" 81 | case .bool: return "Bool" 82 | case .nullable, .anything: return "Any" 83 | case .emptyArray: return "[Any?]" 84 | } 85 | } 86 | 87 | private static func nameForArray(of schema: JSONElementSchema, _ hasOptionalElements: Bool) -> String { 88 | return nameForArray(of: schema.name.toSwiftStructName(), hasOptionalElements: hasOptionalElements) 89 | } 90 | 91 | private static func nameForArray(of valueType: JSONType) -> String { 92 | return nameForArray(of: valueType.swiftTypeName, hasOptionalElements: valueType.isRequired == false) 93 | } 94 | 95 | private static func nameForArray(of typeName: String, hasOptionalElements: Bool) -> String { 96 | let fullTypeName = hasOptionalElements 97 | ? typeName + "?" 98 | : typeName 99 | return "[" + fullTypeName + "]" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /json2swift/swift-code-generation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift-code-generation.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/14/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SwiftCodeGenerator { 12 | /// This method is used when only one Swift file is being generated. 13 | static func generateCodeWithJSONUtilities(for swiftStruct: SwiftStruct) -> String { 14 | return [ 15 | preamble, 16 | "//", 17 | "// MARK: - Data Model", 18 | "//", 19 | swiftStruct.toSwiftCode(), 20 | "", 21 | "//", 22 | "// MARK: - JSON Utilities", 23 | "//", 24 | jsonUtilitiesTemplate, 25 | ""].joined(separator: "\n") 26 | } 27 | 28 | /// This method is used when multiple Swift files are being generated. 29 | static func generateCode(for swiftStruct: SwiftStruct) -> String { 30 | return [ 31 | preamble, 32 | swiftStruct.toSwiftCode(), 33 | ""].joined(separator: "\n") 34 | } 35 | 36 | /// This method is used to only create the JSON utility code once when multiple Swift files are being generated. 37 | static func generateJSONUtilities() -> String { 38 | return [ 39 | preamble, 40 | jsonUtilitiesTemplate, 41 | ""].joined(separator: "\n") 42 | } 43 | 44 | private static let preamble = [ 45 | "// This file was generated by json2swift. https://github.com/ijoshsmith/json2swift", 46 | "", 47 | "import Foundation", 48 | ""].joined(separator: "\n") 49 | } 50 | 51 | 52 | // MARK: - Implementation 53 | 54 | typealias SwiftCode = String 55 | typealias LineOfCode = SwiftCode 56 | 57 | fileprivate struct Indentation { 58 | private let chars: String 59 | private let level: Int 60 | private let value: String 61 | 62 | init(chars: String, level: Int = 0) { 63 | precondition(level >= 0) 64 | self.chars = chars 65 | self.level = level 66 | self.value = String(repeating: chars, count: level) 67 | } 68 | 69 | func apply(toLineOfCode lineOfCode: LineOfCode) -> LineOfCode { 70 | return value + lineOfCode 71 | } 72 | 73 | func apply(toFirstLine firstLine: LineOfCode, 74 | nestedLines generateNestedLines: (Indentation) -> [LineOfCode], 75 | andLastLine lastLine: LineOfCode) -> [LineOfCode] { 76 | let first = apply(toLineOfCode: firstLine) 77 | let middle = generateNestedLines(self.increased()) 78 | let last = apply(toLineOfCode: lastLine) 79 | return [first] + middle + [last] 80 | } 81 | 82 | private func increased() -> Indentation { 83 | return Indentation(chars: chars, level: level + 1) 84 | } 85 | } 86 | 87 | fileprivate extension SwiftStruct { 88 | func toSwiftCode(indentedBy indentChars: String = " ") -> SwiftCode { 89 | let indentation = Indentation(chars: indentChars) 90 | let linesOfCode = toLinesOfCode(at: indentation) 91 | return linesOfCode.joined(separator: "\n") 92 | } 93 | 94 | private func toLinesOfCode(at indentation: Indentation) -> [LineOfCode] { 95 | return indentation.apply( 96 | toFirstLine: "struct \(name): CreatableFromJSON {", 97 | nestedLines: linesOfCodeForMembers(at:), 98 | andLastLine: "}") 99 | } 100 | 101 | private func linesOfCodeForMembers(at indentation: Indentation) -> [LineOfCode] { 102 | return linesOfCodeForProperties(at: indentation) 103 | + initializer.toLinesOfCode(at: indentation) 104 | + failableInitializer.toLinesOfCode(at: indentation) 105 | + linesOfCodeForNestedStructs(at: indentation) 106 | } 107 | 108 | private func linesOfCodeForProperties(at indentation: Indentation) -> [LineOfCode] { 109 | return sortedProperties.map { property in 110 | let propertyCode = property.toLineOfCode() 111 | return indentation.apply(toLineOfCode: propertyCode) 112 | } 113 | } 114 | 115 | private var sortedProperties: [SwiftProperty] { 116 | return properties.sorted { (lhs, rhs) -> Bool in 117 | return lhs.name.compare(rhs.name) == .orderedAscending 118 | } 119 | } 120 | 121 | private func linesOfCodeForNestedStructs(at indentation: Indentation) -> [LineOfCode] { 122 | return sortedNestedStructs.flatMap { $0.toLinesOfCode(at: indentation) } 123 | } 124 | 125 | private var sortedNestedStructs: [SwiftStruct] { 126 | return nestedStructs.sorted(by: { (lhs, rhs) -> Bool in 127 | return lhs.name.compare(rhs.name) == .orderedAscending 128 | }) 129 | } 130 | } 131 | 132 | fileprivate extension SwiftType { 133 | func toSwiftCode() -> SwiftCode { 134 | return isOptional ? name + "?" : name 135 | } 136 | } 137 | 138 | fileprivate extension SwiftProperty { 139 | func toLineOfCode() -> LineOfCode { 140 | return "let \(name): \(type.toSwiftCode())" 141 | } 142 | } 143 | 144 | fileprivate extension SwiftParameter { 145 | func toSwiftCode() -> SwiftCode { 146 | return "\(name): \(type.toSwiftCode())" 147 | } 148 | } 149 | 150 | fileprivate extension SwiftInitializer { 151 | func toLinesOfCode(at indentation: Indentation) -> [LineOfCode] { 152 | return indentation.apply( 153 | toFirstLine: "init(\(parameterList)) {", 154 | nestedLines: linesOfCodeForPropertyAssignments(at:), 155 | andLastLine: "}") 156 | } 157 | 158 | private var parameterList: SwiftCode { 159 | return sortedParameters 160 | .map { $0.toSwiftCode() } 161 | .joined(separator: ", ") 162 | } 163 | 164 | private func linesOfCodeForPropertyAssignments(at indentation: Indentation) -> [LineOfCode] { 165 | return sortedParameters 166 | .map { "self.\($0.name) = \($0.name)" } 167 | .map(indentation.apply(toLineOfCode:)) 168 | } 169 | 170 | private var sortedParameters: [SwiftParameter] { 171 | return parameters.sorted { (lhs, rhs) -> Bool in 172 | return lhs.name.compare(rhs.name) == .orderedAscending 173 | } 174 | } 175 | } 176 | 177 | fileprivate extension SwiftFailableInitializer { 178 | func toLinesOfCode(at indentation: Indentation) -> [LineOfCode] { 179 | return indentation.apply( 180 | toFirstLine: "init?(json: [String: Any]) {", 181 | nestedLines: linesOfCodeInMethodBody(at:), 182 | andLastLine: "}") 183 | } 184 | 185 | private func linesOfCodeInMethodBody(at indentation: Indentation) -> [LineOfCode] { 186 | let linesOfCode = linesOfCodeForTransformations + [lineOfCodeForCallingInitializer] 187 | return linesOfCode.map(indentation.apply(toLineOfCode:)) 188 | } 189 | 190 | private var linesOfCodeForTransformations: [LineOfCode] { 191 | let requiredTransformationLines = sortedRequiredTransformations.map { $0.guardedLetStatement } 192 | let optionalTransformationLines = sortedOptionalTransformations.map { $0.letStatement } 193 | return (requiredTransformationLines + optionalTransformationLines) 194 | } 195 | 196 | private var lineOfCodeForCallingInitializer: LineOfCode { 197 | let sortedPropertyNames = (requiredTransformations + optionalTransformations).map { $0.propertyName }.sorted() 198 | let labeledArguments = sortedPropertyNames.map { $0 + ": " + $0 } 199 | let argumentList = labeledArguments.joined(separator: ", ") 200 | return "self.init(" + argumentList + ")" 201 | } 202 | 203 | private var sortedRequiredTransformations: [TransformationFromJSON] { 204 | return sort(transformations: requiredTransformations) 205 | } 206 | 207 | private var sortedOptionalTransformations: [TransformationFromJSON] { 208 | return sort(transformations: optionalTransformations) 209 | } 210 | 211 | private func sort(transformations: [TransformationFromJSON]) -> [TransformationFromJSON] { 212 | return transformations.sorted { (lhs, rhs) -> Bool in 213 | return lhs.propertyName.compare(rhs.propertyName) == .orderedAscending 214 | } 215 | } 216 | } 217 | 218 | // Internal for unit test access. 219 | internal extension TransformationFromJSON { 220 | var propertyName: String { 221 | switch self { 222 | case let .toCustomStruct(_, propertyName, _): return propertyName 223 | case let .toPrimitiveValue(_, propertyName, _): return propertyName 224 | case let .toCustomStructArray(_, propertyName, _, _): return propertyName 225 | case let .toPrimitiveValueArray(_, propertyName, _, _): return propertyName 226 | } 227 | } 228 | 229 | var guardedLetStatement: LineOfCode { 230 | return "guard \(letStatement) else { return nil }" 231 | } 232 | 233 | var letStatement: LineOfCode { 234 | switch self { 235 | case let .toCustomStruct( attributeName, propertyName, type): return TransformationFromJSON.letStatementForCustomStruct( attributeName, propertyName, type) 236 | case let .toPrimitiveValue( attributeName, propertyName, type): return TransformationFromJSON.letStatementForPrimitiveValue(attributeName, propertyName, type) 237 | case let .toCustomStructArray( attributeName, propertyName, elementType, hasOptionalElements): return TransformationFromJSON.letStatementForCustomStructArray( attributeName, propertyName, elementType, hasOptionalElements) 238 | case let .toPrimitiveValueArray(attributeName, propertyName, elementType, hasOptionalElements): return TransformationFromJSON.letStatementForPrimitiveValueArray(attributeName, propertyName, elementType, hasOptionalElements) 239 | } 240 | } 241 | 242 | private static func letStatementForCustomStruct(_ attributeName: String, _ propertyName: String, _ type: SwiftStruct) -> LineOfCode { 243 | return "let \(propertyName) = \(type.name)(json: json, key: \"\(attributeName)\")" 244 | } 245 | 246 | private static func letStatementForPrimitiveValue(_ attributeName: String, _ propertyName: String, _ type: SwiftPrimitiveValueType) -> LineOfCode { 247 | switch type { 248 | case .any: return "let \(propertyName) = json[\"\(attributeName)\"] as? Any" 249 | case .emptyArray: return "let \(propertyName) = json[\"\(attributeName)\"] as? [Any?]" 250 | case .bool, .int, .string: return "let \(propertyName) = json[\"\(attributeName)\"] as? \(type.name)" 251 | case .double: return "let \(propertyName) = Double(json: json, key: \"\(attributeName)\")" // Allows an integer to be interpreted as a double. 252 | case .url: return "let \(propertyName) = URL(json: json, key: \"\(attributeName)\")" 253 | case .date(let format): return "let \(propertyName) = Date(json: json, key: \"\(attributeName)\", format: \"\(format)\")" 254 | } 255 | } 256 | 257 | private static func letStatementForCustomStructArray(_ attributeName: String, _ propertyName: String, _ elementType: SwiftStruct, _ hasOptionalElements: Bool) -> LineOfCode { 258 | return hasOptionalElements 259 | ? "let \(propertyName) = \(elementType.name).createOptionalInstances(from: json, arrayKey: \"\(attributeName)\")" 260 | : "let \(propertyName) = \(elementType.name).createRequiredInstances(from: json, arrayKey: \"\(attributeName)\")" 261 | } 262 | 263 | private static func letStatementForPrimitiveValueArray(_ attributeName: String, _ propertyName: String, _ elementType: SwiftPrimitiveValueType, _ hasOptionalElements: Bool) -> LineOfCode { 264 | return hasOptionalElements 265 | ? letStatementForArrayOfOptionalPrimitiveValues(attributeName, propertyName, elementType) 266 | : letStatementForArrayOfRequiredPrimitiveValues(attributeName, propertyName, elementType) 267 | } 268 | 269 | private static func letStatementForArrayOfOptionalPrimitiveValues(_ attributeName: String, _ propertyName: String, _ elementType: SwiftPrimitiveValueType) -> LineOfCode { 270 | switch elementType { 271 | case .any, .bool, .int, .string, .emptyArray: return "let \(propertyName) = (json[\"\(attributeName)\"] as? [Any]).map({ $0.toOptionalValueArray() as [\(elementType.name)?] })" 272 | case .date(let format): return "let \(propertyName) = (json[\"\(attributeName)\"] as? [Any]).map({ $0.toOptionalDateArray(withFormat: \"\(format)\") })" 273 | case .double: return "let \(propertyName) = (json[\"\(attributeName)\"] as? [Any]).map({ $0.toOptionalDoubleArray() })" 274 | case .url: return "let \(propertyName) = (json[\"\(attributeName)\"] as? [Any]).map({ $0.toOptionalURLArray() })" 275 | } 276 | } 277 | 278 | private static func letStatementForArrayOfRequiredPrimitiveValues(_ attributeName: String, _ propertyName: String, _ elementType: SwiftPrimitiveValueType) -> LineOfCode { 279 | switch elementType { 280 | case .any: return "let \(propertyName) = json[\"\(attributeName)\"] as? [Any?]" // Any is treated as optional. 281 | case .emptyArray: return "let \(propertyName) = json[\"\(attributeName)\"] as? [[Any?]]" 282 | case .bool, .int, .string: return "let \(propertyName) = json[\"\(attributeName)\"] as? [\(elementType.name)]" 283 | case .date(let format): return "let \(propertyName) = (json[\"\(attributeName)\"] as? [String]).flatMap({ $0.toDateArray(withFormat: \"\(format)\") })" 284 | case .double: return "let \(propertyName) = (json[\"\(attributeName)\"] as? [NSNumber]).map({ $0.toDoubleArray() })" 285 | case .url: return "let \(propertyName) = (json[\"\(attributeName)\"] as? [String]).flatMap({ $0.toURLArray() })" 286 | } 287 | } 288 | } 289 | 290 | fileprivate extension SwiftPrimitiveValueType { 291 | var name: String { 292 | switch self { 293 | case .any: return "Any" 294 | case .bool: return "Bool" 295 | case .date: return "Date" 296 | case .double: return "Double" 297 | case .emptyArray: return "[Any?]" 298 | case .int: return "Int" 299 | case .string: return "String" 300 | case .url: return "URL" 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /json2swift/swift-code-templates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift-code-templates.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/19/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | let jsonUtilitiesTemplate = [ 10 | codeTemplateForCreatableWithJSON, 11 | newline, 12 | codeTemplateForDateParsing, 13 | newline, 14 | codeTemplateForURL, 15 | newline, 16 | codeTemplateForDouble, 17 | newline, 18 | codeTemplateForDoubleArray, 19 | newline, 20 | codeTemplateForStringBasedValueArray, 21 | newline, 22 | codeTemplateForOptionalValueArray 23 | ].flatMap({$0}).joined(separator: "\n") 24 | 25 | private let newline = [""] 26 | 27 | private let codeTemplateForCreatableWithJSON = [ 28 | "/// Adopted by a type that can be instantiated from JSON data.", 29 | "protocol CreatableFromJSON {", 30 | " /// Attempts to configure a new instance of the conforming type with values from a JSON dictionary.", 31 | " init?(json: [String: Any])", 32 | "}", 33 | "", 34 | "extension CreatableFromJSON {", 35 | " /// Attempts to configure a new instance using a JSON dictionary selected by the `key` argument.", 36 | " init?(json: [String: Any], key: String) {", 37 | " guard let jsonDictionary = json[key] as? [String: Any] else { return nil }", 38 | " self.init(json: jsonDictionary)", 39 | " }", 40 | "", 41 | " /// Attempts to produce an array of instances of the conforming type based on an array in the JSON dictionary.", 42 | " /// - Returns: `nil` if the JSON array is missing or if there is an invalid/null element in the JSON array.", 43 | " static func createRequiredInstances(from json: [String: Any], arrayKey: String) -> [Self]? {", 44 | " guard let jsonDictionaries = json[arrayKey] as? [[String: Any]] else { return nil }", 45 | " return createRequiredInstances(from: jsonDictionaries)", 46 | " }", 47 | "", 48 | " /// Attempts to produce an array of instances of the conforming type based on an array of JSON dictionaries.", 49 | " /// - Returns: `nil` if there is an invalid/null element in the JSON array.", 50 | " static func createRequiredInstances(from jsonDictionaries: [[String: Any]]) -> [Self]? {", 51 | " var array = [Self]()", 52 | " for jsonDictionary in jsonDictionaries {", 53 | " guard let instance = Self.init(json: jsonDictionary) else { return nil }", 54 | " array.append(instance)", 55 | " }", 56 | " return array", 57 | " }", 58 | "", 59 | " /// Attempts to produce an array of instances of the conforming type, or `nil`, based on an array in the JSON dictionary.", 60 | " /// - Returns: `nil` if the JSON array is missing, or an array with `nil` for each invalid/null element in the JSON array.", 61 | " static func createOptionalInstances(from json: [String: Any], arrayKey: String) -> [Self?]? {", 62 | " guard let array = json[arrayKey] as? [Any] else { return nil }", 63 | " return createOptionalInstances(from: array)", 64 | " }", 65 | "", 66 | " /// Attempts to produce an array of instances of the conforming type, or `nil`, based on an array.", 67 | " /// - Returns: An array of instances of the conforming type and `nil` for each invalid/null element in the source array.", 68 | " static func createOptionalInstances(from array: [Any]) -> [Self?] {", 69 | " return array.map { item in", 70 | " if let jsonDictionary = item as? [String: Any] {", 71 | " return Self.init(json: jsonDictionary)", 72 | " }", 73 | " else {", 74 | " return nil", 75 | " }", 76 | " }", 77 | " }", 78 | "}"] 79 | 80 | private let codeTemplateForDateParsing = [ 81 | "extension Date {", 82 | " // Date formatters are cached because they are expensive to create. All cache access is performed on a serial queue.", 83 | " private static let cacheQueue = DispatchQueue(label: \"DateFormatterCacheQueue\")", 84 | " private static var formatterCache = [String: DateFormatter]()", 85 | " private static func dateFormatter(with format: String) -> DateFormatter {", 86 | " if let formatter = formatterCache[format] { return formatter }", 87 | " let formatter = DateFormatter()", 88 | " formatter.dateFormat = format", 89 | " formatter.locale = Locale(identifier: \"en_US_POSIX\")", 90 | " formatter.calendar = Calendar(identifier: .gregorian)", 91 | " formatter.timeZone = TimeZone(secondsFromGMT: 0)! // UTC is assumed, but won't interfere with a format-specified time zone.", 92 | " formatterCache[format] = formatter", 93 | " return formatter", 94 | " }", 95 | "", 96 | " static func parse(string: String, format: String) -> Date? {", 97 | " var formatter: DateFormatter!", 98 | " cacheQueue.sync { formatter = dateFormatter(with: format) }", 99 | " return formatter.date(from: string)", 100 | " }", 101 | "", 102 | " init?(json: [String: Any], key: String, format: String) {", 103 | " guard let string = json[key] as? String else { return nil }", 104 | " guard let date = Date.parse(string: string, format: format) else { return nil }", 105 | " self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)", 106 | " }", 107 | "}"] 108 | 109 | private let codeTemplateForURL = [ 110 | "extension URL {", 111 | " init?(json: [String: Any], key: String) {", 112 | " guard let string = json[key] as? String else { return nil }", 113 | " self.init(string: string)", 114 | " }", 115 | "}"] 116 | 117 | private let codeTemplateForDouble = [ 118 | "extension Double {", 119 | " init?(json: [String: Any], key: String) {", 120 | " // Explicitly unboxing the number allows an integer to be converted to a double,", 121 | " // which is needed when a JSON attribute value can have either representation.", 122 | " guard let nsNumber = json[key] as? NSNumber else { return nil }", 123 | " self.init(_: nsNumber.doubleValue)", 124 | " }", 125 | "}"] 126 | 127 | private let codeTemplateForDoubleArray = [ 128 | "extension Array where Element: NSNumber {", 129 | " // Convert integers to doubles, for example [1, 2.0] becomes [1.0, 2.0]", 130 | " // This is necessary because ([1, 2.0] as? [Double]) yields nil.", 131 | " func toDoubleArray() -> [Double] {", 132 | " return map { $0.doubleValue }", 133 | " }", 134 | "}"] 135 | 136 | private let codeTemplateForStringBasedValueArray = [ 137 | "extension Array where Element: CustomStringConvertible {", 138 | " func toDateArray(withFormat format: String) -> [Date]? {", 139 | " var dateArray = [Date]()", 140 | " for string in self {", 141 | " guard let date = Date.parse(string: String(describing: string), format: format) else { return nil }", 142 | " dateArray.append(date)", 143 | " }", 144 | " return dateArray", 145 | " }", 146 | "", 147 | " func toURLArray() -> [URL]? {", 148 | " var urlArray = [URL]()", 149 | " for string in self {", 150 | " guard let url = URL(string: String(describing: string)) else { return nil }", 151 | " urlArray.append(url)", 152 | " }", 153 | " return urlArray", 154 | " }", 155 | "}"] 156 | 157 | private let codeTemplateForOptionalValueArray = [ 158 | "extension Array where Element: Any {", 159 | " func toOptionalValueArray() -> [Value?] {", 160 | " return map { ($0 is NSNull) ? nil : ($0 as? Value) }", 161 | " }", 162 | "", 163 | " func toOptionalDateArray(withFormat format: String) -> [Date?] {", 164 | " return map { item in", 165 | " guard let string = item as? String else { return nil }", 166 | " return Date.parse(string: string, format: format)", 167 | " }", 168 | " }", 169 | "", 170 | " func toOptionalDoubleArray() -> [Double?] {", 171 | " return map { item in", 172 | " guard let nsNumber = item as? NSNumber else { return nil }", 173 | " return nsNumber.doubleValue", 174 | " }", 175 | " }", 176 | "", 177 | " func toOptionalURLArray() -> [URL?] {", 178 | " return map { item in", 179 | " guard let string = item as? String else { return nil }", 180 | " return URL(string: string)", 181 | " }", 182 | " }", 183 | "}"] 184 | -------------------------------------------------------------------------------- /json2swift/swift-data-model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift-data-model.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/14/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SwiftStruct { 12 | let name: String 13 | let properties: [SwiftProperty] 14 | let initializer: SwiftInitializer 15 | let failableInitializer: SwiftFailableInitializer 16 | let nestedStructs: [SwiftStruct] 17 | } 18 | 19 | struct SwiftProperty { 20 | let name: String 21 | let type: SwiftType 22 | } 23 | 24 | struct SwiftType { 25 | let name: String 26 | let isOptional: Bool 27 | } 28 | 29 | struct SwiftInitializer { 30 | let parameters: [SwiftParameter] 31 | } 32 | 33 | struct SwiftParameter { 34 | let name: String 35 | let type: SwiftType 36 | } 37 | 38 | struct SwiftFailableInitializer { 39 | let requiredTransformations: [TransformationFromJSON] 40 | let optionalTransformations: [TransformationFromJSON] 41 | } 42 | 43 | enum TransformationFromJSON { 44 | case toCustomStruct( attributeName: String, propertyName: String, type: SwiftStruct) 45 | case toPrimitiveValue( attributeName: String, propertyName: String, type: SwiftPrimitiveValueType) 46 | case toCustomStructArray( attributeName: String, propertyName: String, elementType: SwiftStruct, hasOptionalElements: Bool) 47 | case toPrimitiveValueArray(attributeName: String, propertyName: String, elementType: SwiftPrimitiveValueType, hasOptionalElements: Bool) 48 | } 49 | 50 | enum SwiftPrimitiveValueType { 51 | case int 52 | case double 53 | case date(format: String) 54 | case url 55 | case string 56 | case bool 57 | case any 58 | case emptyArray 59 | } 60 | -------------------------------------------------------------------------------- /unit_tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /unit_tests/JSONType+JSONType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONType+JSONType.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/14/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | extension JSONType { 10 | /// A shortcut function that helps create concise unit tests. 11 | static func +(lhs: JSONType, rhs: JSONType) -> JSONType { 12 | return lhs.findCompatibleType(with: rhs) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /unit_tests/json-attribute-merging-boolean-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging-boolean-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/11/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let rb = JSONType.bool(isRequired: true) 12 | private let ob = JSONType.bool(isRequired: false) 13 | 14 | class json_attribute_merging_boolean_tests: XCTestCase { 15 | /* 16 | Method Naming Convention 17 | r = Required 18 | o = Optional 19 | b = Boolean 20 | */ 21 | 22 | // MARK: - Bool and Bool 23 | func test_rb_and_rb_yields_rb() { XCTAssertEqual(rb + rb, rb) } 24 | func test_ob_and_ob_yields_ob() { XCTAssertEqual(ob + ob, ob) } 25 | func test_ob_and_rb_yields_ob() { XCTAssertEqual(ob + rb, ob) } 26 | func test_rb_and_ob_yields_ob() { XCTAssertEqual(rb + ob, ob) } 27 | 28 | // MARK: - Bool and Nullable 29 | func test_rb_and_nullable_yields_ob() { XCTAssertEqual(rb + .nullable, ob) } 30 | func test_ob_and_nullable_yields_ob() { XCTAssertEqual(ob + .nullable, ob) } 31 | func test_nullable_and_rb_yields_ob() { XCTAssertEqual(.nullable + rb, ob) } 32 | func test_nullable_and_ob_yields_ob() { XCTAssertEqual(.nullable + ob, ob) } 33 | } 34 | -------------------------------------------------------------------------------- /unit_tests/json-attribute-merging-element-array-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging-element-array-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/12/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let stringAttributeName = "string-attribute" 12 | private let boolAttributeName = "bool-attribute" 13 | private let dateAttributeName = "date-attribute" 14 | private let schemaX = JSONElementSchema(name: "schema", attributes: [ 15 | stringAttributeName: .string(isRequired: false), 16 | boolAttributeName: .bool( isRequired: true), 17 | dateAttributeName: .date( isRequired: true, format: "MM/dd/yyyy") 18 | ]) 19 | private let schemaY = JSONElementSchema(name: "schema", attributes: [ 20 | stringAttributeName: .string(isRequired: true), 21 | boolAttributeName: .bool( isRequired: false), 22 | dateAttributeName: .date( isRequired: true, format: "MM/dd/yyyy") 23 | ]) 24 | private let schemaM = JSONElementSchema(name: "schema", attributes: [ 25 | stringAttributeName: .string(isRequired: false), 26 | boolAttributeName: .bool( isRequired: false), 27 | dateAttributeName: .date( isRequired: true, format: "MM/dd/yyyy") 28 | ]) 29 | private let rax = JSONType.elementArray(isRequired: true, elementSchema: schemaX, hasNullableElements: false) 30 | private let oax = JSONType.elementArray(isRequired: false, elementSchema: schemaX, hasNullableElements: false) 31 | private let ray = JSONType.elementArray(isRequired: true, elementSchema: schemaY, hasNullableElements: false) 32 | private let oay = JSONType.elementArray(isRequired: false, elementSchema: schemaY, hasNullableElements: false) 33 | private let ram = JSONType.elementArray(isRequired: true, elementSchema: schemaM, hasNullableElements: false) 34 | private let oam = JSONType.elementArray(isRequired: false, elementSchema: schemaM, hasNullableElements: false) 35 | 36 | private let raxn = JSONType.elementArray(isRequired: true, elementSchema: schemaX, hasNullableElements: true) 37 | private let oaxn = JSONType.elementArray(isRequired: false, elementSchema: schemaX, hasNullableElements: true) 38 | 39 | class json_attribute_merging_element_array_tests: XCTestCase { 40 | /* 41 | Method Naming Convention 42 | r = Required 43 | o = Optional 44 | n = Nullable Elements 45 | a = Array 46 | x = Schema X 47 | y = Schema Y 48 | m = Merged Schema (X + Y) 49 | */ 50 | 51 | // MARK: - Array of Non-Nullable Elements and Nullable 52 | func test_rax_and_nullable_yields_oax() { XCTAssertEqual(rax + .nullable, oax) } 53 | func test_oax_and_nullable_yields_oax() { XCTAssertEqual(oax + .nullable, oax) } 54 | func test_nullable_and_rax_yields_oax() { XCTAssertEqual(.nullable + rax, oax) } 55 | func test_nullable_and_oax_yields_oax() { XCTAssertEqual(.nullable + oax, oax) } 56 | 57 | // MARK: - Array of Nullable Elements and Nullable 58 | func test_raxn_and_nullable_yields_oaxn() { XCTAssertEqual(raxn + .nullable, oaxn) } 59 | func test_oaxn_and_nullable_yields_oaxn() { XCTAssertEqual(oaxn + .nullable, oaxn) } 60 | func test_nullable_and_raxn_yields_oaxn() { XCTAssertEqual(.nullable + raxn, oaxn) } 61 | func test_nullable_and_oaxn_yields_oaxn() { XCTAssertEqual(.nullable + oaxn, oaxn) } 62 | 63 | // MARK: - Arrays with same schema 64 | func test_ra_and_ra_yields_ra() { XCTAssertEqual(rax + rax, rax) } 65 | func test_ra_and_oa_yields_oa() { XCTAssertEqual(rax + oax, oax) } 66 | func test_oa_and_ra_yields_oa() { XCTAssertEqual(oax + rax, oax) } 67 | func test_oa_and_oa_yields_oa() { XCTAssertEqual(oax + oax, oax) } 68 | 69 | // MARK: - Arrays with different schemas 70 | func test_rax_and_ray_yields_ram() { XCTAssertEqual(rax + ray, ram) } 71 | func test_ray_and_rax_yields_ram() { XCTAssertEqual(ray + rax, ram) } 72 | func test_rax_and_oay_yields_oam() { XCTAssertEqual(rax + oay, oam) } 73 | func test_ray_and_oax_yields_oam() { XCTAssertEqual(ray + oax, oam) } 74 | func test_oax_and_ray_yields_oam() { XCTAssertEqual(oax + ray, oam) } 75 | func test_oay_and_rax_yields_oam() { XCTAssertEqual(oay + rax, oam) } 76 | func test_oax_and_oay_yields_oam() { XCTAssertEqual(oax + oay, oam) } 77 | func test_oay_and_oax_yields_oam() { XCTAssertEqual(oay + oax, oam) } 78 | } 79 | -------------------------------------------------------------------------------- /unit_tests/json-attribute-merging-element-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging-element-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/12/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let stringAttributeName = "string-attribute" 12 | private let boolAttributeName = "bool-attribute" 13 | private let dateAttributeName = "date-attribute" 14 | private let schemaX = JSONElementSchema(name: "schema", attributes: [ 15 | stringAttributeName: .string(isRequired: false), 16 | boolAttributeName: .bool( isRequired: true), 17 | dateAttributeName: .date( isRequired: true, format: "MM/dd/yyyy") 18 | ]) 19 | private let schemaY = JSONElementSchema(name: "schema", attributes: [ 20 | stringAttributeName: .string(isRequired: true), 21 | boolAttributeName: .bool( isRequired: false), 22 | dateAttributeName: .date( isRequired: true, format: "MM/dd/yyyy") 23 | ]) 24 | private let schemaM = JSONElementSchema(name: "schema", attributes: [ 25 | stringAttributeName: .string(isRequired: false), 26 | boolAttributeName: .bool( isRequired: false), 27 | dateAttributeName: .date( isRequired: true, format: "MM/dd/yyyy") 28 | ]) 29 | private let rex = JSONType.element(isRequired: true, schema: schemaX) 30 | private let oex = JSONType.element(isRequired: false, schema: schemaX) 31 | private let rey = JSONType.element(isRequired: true, schema: schemaY) 32 | private let oey = JSONType.element(isRequired: false, schema: schemaY) 33 | private let rem = JSONType.element(isRequired: true, schema: schemaM) 34 | private let oem = JSONType.element(isRequired: false, schema: schemaM) 35 | 36 | class json_attribute_merging_element_tests: XCTestCase { 37 | /* 38 | Method Naming Convention 39 | r = Required 40 | o = Optional 41 | e = Element 42 | x = Schema X 43 | y = Schema Y 44 | m = Merged Schema (X + Y) 45 | */ 46 | 47 | // MARK: - Element and Nullable 48 | func test_re_and_nullable_yields_oe() { XCTAssertEqual(rex + .nullable, oex) } 49 | func test_oe_and_nullable_yields_oe() { XCTAssertEqual(oex + .nullable, oex) } 50 | func test_nullable_and_re_yields_oe() { XCTAssertEqual(.nullable + rex, oex) } 51 | func test_nullable_and_oe_yields_oe() { XCTAssertEqual(.nullable + oex, oex) } 52 | 53 | // MARK: - Elements with same schema 54 | func test_re_and_re_yields_re() { XCTAssertEqual(rex + rex, rex) } 55 | func test_re_and_oe_yields_oe() { XCTAssertEqual(rex + oex, oex) } 56 | func test_oe_and_re_yields_oe() { XCTAssertEqual(oex + rex, oex) } 57 | func test_oe_and_oe_yields_oe() { XCTAssertEqual(oex + oex, oex) } 58 | 59 | // MARK: - Elements with different schemas 60 | func test_rex_and_rey_yields_rem() { XCTAssertEqual(rex + rey, rem) } 61 | func test_rey_and_rex_yields_rem() { XCTAssertEqual(rey + rex, rem) } 62 | func test_rex_and_oey_yields_oem() { XCTAssertEqual(rex + oey, oem) } 63 | func test_rey_and_oex_yields_oem() { XCTAssertEqual(rey + oex, oem) } 64 | func test_oex_and_rey_yields_oem() { XCTAssertEqual(oex + rey, oem) } 65 | func test_oey_and_rex_yields_oem() { XCTAssertEqual(oey + rex, oem) } 66 | func test_oex_and_oey_yields_oem() { XCTAssertEqual(oex + oey, oem) } 67 | func test_oey_and_oex_yields_oem() { XCTAssertEqual(oey + oex, oem) } 68 | } 69 | -------------------------------------------------------------------------------- /unit_tests/json-attribute-merging-empty-array-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging-empty-array-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/29/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let schemaX = JSONElementSchema(name: "schema", attributes: [:]) 12 | 13 | private let rax = JSONType.elementArray(isRequired: true, elementSchema: schemaX, hasNullableElements: false) 14 | private let oax = JSONType.elementArray(isRequired: false, elementSchema: schemaX, hasNullableElements: false) 15 | 16 | private let ras = JSONType.valueArray(isRequired: true, valueType: .string(isRequired: true)) 17 | private let oas = JSONType.valueArray(isRequired: false, valueType: .string(isRequired: true)) 18 | 19 | class json_attribute_merging_empty_array_tests: XCTestCase { 20 | /* 21 | Method Naming Convention 22 | r = Required 23 | o = Optional 24 | a = Array 25 | x = Schema X 26 | s = String 27 | */ 28 | 29 | // MARK: - Empty Array and Empty Array 30 | func test_emptyArray_and_emptyArray_yields_emptyArray() { XCTAssertEqual(.emptyArray + .emptyArray, .emptyArray) } 31 | 32 | // MARK: - Element Array and Empty Array 33 | func test_rax_and_emptyArray_yields_rax() { XCTAssertEqual(rax + .emptyArray, rax) } 34 | func test_oax_and_emptyArray_yields_oax() { XCTAssertEqual(oax + .emptyArray, oax) } 35 | func test_emptyArray_and_rax_yields_rax() { XCTAssertEqual(.emptyArray + rax, rax) } 36 | func test_emptyArray_and_oax_yields_oax() { XCTAssertEqual(.emptyArray + oax, oax) } 37 | 38 | // MARK: - Value Array and Empty Array 39 | func test_ras_and_emptyArray_yields_ras() { XCTAssertEqual(ras + .emptyArray, ras) } 40 | func test_oas_and_emptyArray_yields_oas() { XCTAssertEqual(oas + .emptyArray, oas) } 41 | func test_emptyArray_and_ras_yields_ras() { XCTAssertEqual(.emptyArray + ras, ras) } 42 | func test_emptyArray_and_oas_yields_oas() { XCTAssertEqual(.emptyArray + oas, oas) } 43 | } 44 | -------------------------------------------------------------------------------- /unit_tests/json-attribute-merging-numeric-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging-numeric-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/11/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let ri = JSONType.number(isRequired: true, isFloatingPoint: false) 12 | private let oi = JSONType.number(isRequired: false, isFloatingPoint: false) 13 | private let rf = JSONType.number(isRequired: true, isFloatingPoint: true) 14 | private let of = JSONType.number(isRequired: false, isFloatingPoint: true) 15 | 16 | class json_attribute_merging_numeric_tests: XCTestCase { 17 | /* 18 | Method Naming Convention 19 | r = Required 20 | o = Optional 21 | i = Integral 22 | f = Floating Point 23 | */ 24 | 25 | // MARK: - Both are required 26 | func test_ri_and_ri_yields_ri() { XCTAssertEqual(ri + ri, ri) } 27 | func test_ri_and_rf_yields_rf() { XCTAssertEqual(ri + rf, rf) } 28 | func test_rf_and_ri_yields_rf() { XCTAssertEqual(rf + ri, rf) } 29 | func test_rf_and_rf_yields_rf() { XCTAssertEqual(rf + rf, rf) } 30 | 31 | // MARK: - Both are optional 32 | func test_oi_and_oi_yields_oi() { XCTAssertEqual(oi + oi, oi) } 33 | func test_oi_and_of_yields_of() { XCTAssertEqual(oi + of, of) } 34 | func test_of_and_oi_yields_of() { XCTAssertEqual(of + oi, of) } 35 | func test_of_and_of_yields_of() { XCTAssertEqual(of + of, of) } 36 | 37 | // MARK: - One optional, one required 38 | func test_oi_and_ri_yields_oi() { XCTAssertEqual(oi + ri, oi) } 39 | func test_of_and_rf_yields_of() { XCTAssertEqual(of + rf, of) } 40 | func test_ri_and_oi_yields_oi() { XCTAssertEqual(ri + oi, oi) } 41 | func test_rf_and_of_yields_of() { XCTAssertEqual(rf + of, of) } 42 | 43 | // MARK: - One is a required number, the other is nullable 44 | func test_ri_and_nullable_yields_oi() { XCTAssertEqual(ri + .nullable, oi) } 45 | func test_rf_and_nullable_yields_of() { XCTAssertEqual(rf + .nullable, of) } 46 | func test_nullable_and_ri_yields_oi() { XCTAssertEqual(.nullable + ri, oi) } 47 | func test_nullable_and_rf_yields_of() { XCTAssertEqual(.nullable + rf, of) } 48 | 49 | // MARK: - One is an optional number, the other is nullable 50 | func test_oi_and_nullable_yields_oi() { XCTAssertEqual(oi + .nullable, oi) } 51 | func test_of_and_nullable_yields_of() { XCTAssertEqual(of + .nullable, of) } 52 | func test_nullable_and_oi_yields_oi() { XCTAssertEqual(.nullable + oi, oi) } 53 | func test_nullable_and_of_yields_of() { XCTAssertEqual(.nullable + of, of) } 54 | } 55 | -------------------------------------------------------------------------------- /unit_tests/json-attribute-merging-text-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging-text-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/11/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let rd = JSONType.date(isRequired: true, format: "M/d/yyyy") 12 | private let od = JSONType.date(isRequired: false, format: "M/d/yyyy") 13 | private let ru = JSONType.url(isRequired: true) 14 | private let ou = JSONType.url(isRequired: false) 15 | private let rs = JSONType.string(isRequired: true) 16 | private let os = JSONType.string(isRequired: false) 17 | 18 | class json_attribute_merging_text_tests: XCTestCase { 19 | /* 20 | Method Naming Convention 21 | r = Required 22 | o = Optional 23 | d = Date 24 | u = URL 25 | s = String 26 | */ 27 | 28 | // MARK: - String and String yields String 29 | func test_rs_and_rs_yields_rs() { XCTAssertEqual(rs + rs, rs) } 30 | func test_rs_and_os_yields_os() { XCTAssertEqual(rs + os, os) } 31 | func test_os_and_rs_yields_os() { XCTAssertEqual(os + rs, os) } 32 | func test_os_and_os_yields_os() { XCTAssertEqual(os + os, os) } 33 | 34 | // MARK: - String and Nullable yields Optional String 35 | func test_rs_and_nullable_yields_os() { XCTAssertEqual(rs + .nullable, os) } 36 | func test_os_and_nullable_yields_os() { XCTAssertEqual(os + .nullable, os) } 37 | func test_nullable_and_rs_yields_os() { XCTAssertEqual(.nullable + rs, os) } 38 | func test_nullable_and_os_yields_os() { XCTAssertEqual(.nullable + os, os) } 39 | 40 | // MARK: - URL and URL yields URL 41 | func test_ru_and_ru_yields_ru() { XCTAssertEqual(ru + ru, ru) } 42 | func test_ru_and_ou_yields_ou() { XCTAssertEqual(ru + ou, ou) } 43 | func test_ou_and_ru_yields_ou() { XCTAssertEqual(ou + ru, ou) } 44 | func test_ou_and_ou_yields_ou() { XCTAssertEqual(ou + ou, ou) } 45 | 46 | // MARK: - URL and Nullable yields Optional URL 47 | func test_ru_and_nullable_yields_ou() { XCTAssertEqual(ru + .nullable, ou) } 48 | func test_ou_and_nullable_yields_ou() { XCTAssertEqual(ou + .nullable, ou) } 49 | func test_nullable_and_ru_yields_ou() { XCTAssertEqual(.nullable + ru, ou) } 50 | func test_nullable_and_ou_yields_ou() { XCTAssertEqual(.nullable + ou, ou) } 51 | 52 | // MARK: - URL and String yields String 53 | func test_ru_and_rs_yields_rs() { XCTAssertEqual(ru + rs, rs) } 54 | func test_ru_and_os_yields_os() { XCTAssertEqual(ru + os, os) } 55 | func test_ou_and_rs_yields_os() { XCTAssertEqual(ou + rs, os) } 56 | func test_ou_and_os_yields_os() { XCTAssertEqual(ou + os, os) } 57 | 58 | // MARK: - Date and Date yields Date 59 | func test_rd_and_rd_yields_rd() { XCTAssertEqual(rd + rd, rd) } 60 | func test_rd_and_od_yields_od() { XCTAssertEqual(rd + od, od) } 61 | func test_od_and_rd_yields_od() { XCTAssertEqual(od + rd, od) } 62 | func test_od_and_od_yields_od() { XCTAssertEqual(od + od, od) } 63 | 64 | // MARK: - Different date formats 65 | func test_different_date_formats_yields_first_format() { 66 | let dateTypeA = JSONType.date(isRequired: true, format: "first-format") 67 | let dateTypeB = JSONType.date(isRequired: true, format: "second-format") 68 | XCTAssertEqual(dateTypeA + dateTypeB, dateTypeA) 69 | } 70 | 71 | // MARK: - Date and Nullable yields Optional Date 72 | func test_rd_and_nullable_yields_od() { XCTAssertEqual(rd + .nullable, od) } 73 | func test_od_and_nullable_yields_od() { XCTAssertEqual(od + .nullable, od) } 74 | func test_nullable_and_rd_yields_od() { XCTAssertEqual(.nullable + rd, od) } 75 | func test_nullable_and_od_yields_od() { XCTAssertEqual(.nullable + od, od) } 76 | 77 | // MARK: - Date and URL yields String 78 | func test_rd_and_ru_yields_rs() { XCTAssertEqual(rd + ru, rs) } 79 | func test_rd_and_ou_yields_os() { XCTAssertEqual(rd + ou, os) } 80 | func test_od_and_ru_yields_os() { XCTAssertEqual(od + ru, os) } 81 | func test_od_and_ou_yields_os() { XCTAssertEqual(od + ou, os) } 82 | 83 | // MARK: - Date and String yields Date (to allow for only one attribute to need a DATE_FORMAT) 84 | func test_rd_and_rs_yields_rd() { XCTAssertEqual(rd + rs, rd) } 85 | func test_rd_and_os_yields_od() { XCTAssertEqual(rd + os, od) } 86 | func test_od_and_rs_yields_od() { XCTAssertEqual(od + rs, od) } 87 | func test_od_and_os_yields_od() { XCTAssertEqual(od + os, od) } 88 | } 89 | -------------------------------------------------------------------------------- /unit_tests/json-attribute-merging-value-array-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-attribute-merging-value-array-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/14/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let rars = JSONType.valueArray(isRequired: true, valueType: .string(isRequired: true)) 12 | private let raos = JSONType.valueArray(isRequired: true, valueType: .string(isRequired: false)) 13 | private let oars = JSONType.valueArray(isRequired: false, valueType: .string(isRequired: true)) 14 | private let oaos = JSONType.valueArray(isRequired: false, valueType: .string(isRequired: false)) 15 | 16 | class json_attribute_merging_value_array_tests: XCTestCase { 17 | /* 18 | Method Naming Convention 19 | r = Required 20 | o = Optional 21 | a = Array 22 | s = String 23 | */ 24 | 25 | // MARK: - Arrays with same value type 26 | func test_rars_and_rars_yields_rars() { XCTAssertEqual(rars + rars, rars) } 27 | func test_rars_and_raos_yields_raos() { XCTAssertEqual(rars + raos, raos) } 28 | func test_raos_and_rars_yields_raos() { XCTAssertEqual(raos + rars, raos) } 29 | func test_raos_and_raos_yields_raos() { XCTAssertEqual(raos + raos, raos) } 30 | func test_rars_and_oars_yields_oars() { XCTAssertEqual(rars + oars, oars) } 31 | func test_rars_and_oaos_yields_oaos() { XCTAssertEqual(rars + oaos, oaos) } 32 | func test_raos_and_oars_yields_oaos() { XCTAssertEqual(raos + oars, oaos) } 33 | func test_raos_and_oaos_yields_oaos() { XCTAssertEqual(raos + oaos, oaos) } 34 | func test_oars_and_rars_yields_oars() { XCTAssertEqual(oars + rars, oars) } 35 | func test_oars_and_raos_yields_oaos() { XCTAssertEqual(oars + raos, oaos) } 36 | func test_oaos_and_rars_yields_oaos() { XCTAssertEqual(oaos + rars, oaos) } 37 | func test_oaos_and_raos_yields_oaos() { XCTAssertEqual(oaos + raos, oaos) } 38 | func test_oars_and_oars_yields_oars() { XCTAssertEqual(oars + oars, oars) } 39 | func test_oars_and_oaos_yields_oaos() { XCTAssertEqual(oars + oaos, oaos) } 40 | func test_oaos_and_oars_yields_oaos() { XCTAssertEqual(oaos + oars, oaos) } 41 | func test_oaos_and_oaos_yields_oaos() { XCTAssertEqual(oaos + oaos, oaos) } 42 | } 43 | -------------------------------------------------------------------------------- /unit_tests/json-schema-inference-element-array-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-schema-inference-element-array-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/13/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let dummyName = "fake-array" 12 | 13 | class json_schema_inference_element_array_tests: XCTestCase { 14 | func test_empty_array() { 15 | let elementArray = [JSONElement]() 16 | let schema = JSONElementSchema.inferred(from: elementArray, named: dummyName) 17 | XCTAssertEqual(schema.name, dummyName) 18 | XCTAssertTrue(schema.attributes.isEmpty) 19 | } 20 | 21 | func test_one_element_array() { 22 | let elementArray = [ 23 | [ 24 | "numeric-attribute": 42 25 | ] 26 | ] 27 | let schema = JSONElementSchema.inferred(from: elementArray, named: dummyName) 28 | if let attribute = schema.attributes["numeric-attribute"] { 29 | XCTAssertEqual(attribute, .number(isRequired: true, isFloatingPoint: false)) 30 | } 31 | else { XCTFail() } 32 | } 33 | 34 | func test_two_element_array() { 35 | let elementArray = [ 36 | [ 37 | "numeric-attribute": 42 38 | ], 39 | [ 40 | "numeric-attribute": 3.14 41 | ] 42 | ] 43 | let schema = JSONElementSchema.inferred(from: elementArray, named: dummyName) 44 | if let attribute = schema.attributes["numeric-attribute"] { 45 | XCTAssertEqual(attribute, .number(isRequired: true, isFloatingPoint: true)) 46 | } 47 | else { XCTFail() } 48 | } 49 | 50 | func test_three_element_array() { 51 | let elementArray = [ 52 | [ 53 | "url-attribute": "http://abc.com/kitty.png" 54 | ], 55 | [ 56 | "url-attribute": NSNull() 57 | ], 58 | [ 59 | "url-attribute": "http://xyz.org/foo?q=bar" 60 | ] 61 | ] 62 | let schema = JSONElementSchema.inferred(from: elementArray, named: dummyName) 63 | if let attribute = schema.attributes["url-attribute"] { 64 | XCTAssertEqual(attribute, .url(isRequired: false)) 65 | } 66 | else { XCTFail() } 67 | } 68 | 69 | func test_three_element_array_with_two_attributes_per_element() { 70 | let elementArray = [ 71 | [ 72 | "string-attribute": "apple", 73 | "bool-attribute": true 74 | ], 75 | [ 76 | "string-attribute": "banana", 77 | "bool-attribute": false 78 | ], 79 | [ 80 | "string-attribute": "cherry" 81 | // Omitting bool-attribute makes it optional. 82 | ] 83 | ] 84 | let schema = JSONElementSchema.inferred(from: elementArray, named: dummyName) 85 | XCTAssertEqual(schema.attributes.count, 2) 86 | 87 | if let stringAttribute = schema.attributes["string-attribute"] { 88 | XCTAssertEqual(stringAttribute, .string(isRequired: true)) 89 | } 90 | else { XCTFail() } 91 | 92 | if let boolAttribute = schema.attributes["bool-attribute"] { 93 | XCTAssertEqual(boolAttribute, .bool(isRequired: false)) 94 | } 95 | else { XCTFail() } 96 | } 97 | 98 | func test_element_array_with_null_value() { 99 | let elementArray: Any = [ 100 | [ 101 | "string-attribute": "apple", 102 | "bool-attribute": true 103 | ], 104 | NSNull(), 105 | [ 106 | "string-attribute": "banana", 107 | "bool-attribute": false 108 | ] 109 | ] 110 | let schema = JSONElementSchema.inferred(from: ["array-attribute": elementArray], named: "foo") 111 | XCTAssertEqual(schema.attributes.count, 1) 112 | 113 | if let arrayAttribute = schema.attributes["array-attribute"] { 114 | if case let .elementArray(isRequired, elementSchema, hasNullableElements) = arrayAttribute { 115 | XCTAssertTrue(isRequired) 116 | XCTAssertEqual(elementSchema.name, "array-attribute") 117 | XCTAssertEqual(elementSchema.attributes.count, 2) 118 | XCTAssertTrue(hasNullableElements) 119 | } 120 | else { XCTFail() } 121 | } 122 | else { XCTFail() } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /unit_tests/json-schema-inference-element-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-schema-inference-element-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/10/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let dummyName = "fake-element" 12 | 13 | class json_schema_inference_element_tests: XCTestCase { 14 | func test_null() { 15 | let jsonElement = ["null-attribute": NSNull()] 16 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 17 | if let type = schema.attributes["null-attribute"] { 18 | XCTAssertEqual(type, .nullable) 19 | } 20 | else { XCTFail() } 21 | } 22 | 23 | func test_bool() { 24 | let jsonElement = ["bool-attribute": true] 25 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 26 | if let type = schema.attributes["bool-attribute"] { 27 | XCTAssertEqual(type, .bool(isRequired: true)) 28 | } 29 | else { XCTFail() } 30 | } 31 | 32 | func test_integer() { 33 | let jsonElement = ["int-attribute": 42] 34 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 35 | if let type = schema.attributes["int-attribute"] { 36 | XCTAssertEqual(type, .number(isRequired: true, isFloatingPoint: false)) 37 | } 38 | else { XCTFail() } 39 | } 40 | 41 | func test_floating_point() { 42 | let jsonElement = ["floating-point-attribute": 3.14] 43 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 44 | if let type = schema.attributes["floating-point-attribute"] { 45 | XCTAssertEqual(type, .number(isRequired: true, isFloatingPoint: true)) 46 | } 47 | else { XCTFail() } 48 | } 49 | 50 | func test_string() { 51 | let jsonElement = ["string-attribute": "some text"] 52 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 53 | if let type = schema.attributes["string-attribute"] { 54 | XCTAssertEqual(type, .string(isRequired: true)) 55 | } 56 | else { XCTFail() } 57 | } 58 | 59 | func test_url() { 60 | let jsonElement = ["url-attribute": "http://ijoshsmith.com"] 61 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 62 | if let type = schema.attributes["url-attribute"] { 63 | XCTAssertEqual(type, .url(isRequired: true)) 64 | } 65 | else { XCTFail() } 66 | } 67 | 68 | func test_date() { 69 | let jsonElement = ["date-attribute": "DATE_FORMAT=MM/dd/yyyy"] 70 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 71 | if let type = schema.attributes["date-attribute"] { 72 | XCTAssertEqual(type, .date(isRequired: true, format: "MM/dd/yyyy")) 73 | } 74 | else { XCTFail() } 75 | } 76 | 77 | func test_element() { 78 | let jsonElement = [ 79 | "element-attribute": [ 80 | "string-attribute": "some text" 81 | ] 82 | ] 83 | let schema = JSONElementSchema.inferred(from: jsonElement, named: dummyName) 84 | if let type = schema.attributes["element-attribute"] { 85 | let expectedSchema = JSONElementSchema( 86 | name: "element-attribute", 87 | attributes: ["string-attribute": .string(isRequired: true)]) 88 | XCTAssertEqual(type, .element(isRequired: true, schema: expectedSchema)) 89 | } 90 | else { XCTFail() } 91 | } 92 | 93 | func test_element_array() { 94 | let jsonElementArray = [ 95 | "array-attribute": [ 96 | [ 97 | "string-attribute": "some text" 98 | ] 99 | ] 100 | ] 101 | let schema = JSONElementSchema.inferred(from: jsonElementArray, named: dummyName) 102 | if let type = schema.attributes["array-attribute"] { 103 | let expectedSchema = JSONElementSchema( 104 | name: "array-attribute", 105 | attributes: ["string-attribute": .string(isRequired: true)]) 106 | XCTAssertEqual(type, .elementArray(isRequired: true, elementSchema: expectedSchema, hasNullableElements: false)) 107 | } 108 | else { XCTFail() } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /unit_tests/json-schema-inference-value-array-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // json-schema-inference-value-array-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/14/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | private let dummyName = "fake-element" 12 | 13 | class json_schema_inference_value_array_tests: XCTestCase { 14 | func test_array_of_integer() { 15 | let valuesArray = [ 16 | "integers-attribute": [ 17 | 1, 2, 3 18 | ] 19 | ] 20 | let schema = JSONElementSchema.inferred(from: valuesArray, named: dummyName) 21 | if let type = schema.attributes["integers-attribute"] { 22 | let arrayOfInteger: JSONType = 23 | .valueArray(isRequired: true, valueType: 24 | .number(isRequired: true, isFloatingPoint: false)) 25 | XCTAssertEqual(type, arrayOfInteger) 26 | } 27 | else { XCTFail() } 28 | } 29 | 30 | func test_array_of_array_of_integer() { 31 | let valuesArray = [ 32 | "integer-arrays-attribute": [ 33 | [1, 2, 3], 34 | [4, 5], 35 | [6], 36 | ] 37 | ] 38 | let schema = JSONElementSchema.inferred(from: valuesArray, named: dummyName) 39 | if let type = schema.attributes["integer-arrays-attribute"] { 40 | let arrayOfArrayOfInteger: JSONType = 41 | .valueArray(isRequired: true, valueType: 42 | .valueArray(isRequired: true, valueType: 43 | .number(isRequired: true, isFloatingPoint: false))) 44 | XCTAssertEqual(type, arrayOfArrayOfInteger) 45 | } 46 | else { XCTFail() } 47 | } 48 | 49 | func test_array_of_array_of_optional_bool() { 50 | let valuesArray = [ 51 | "boolean-arrays-attribute": [ 52 | [true, false, true], 53 | [false, NSNull(), false], 54 | [false, false, true], 55 | ] 56 | ] 57 | let schema = JSONElementSchema.inferred(from: valuesArray, named: dummyName) 58 | if let type = schema.attributes["boolean-arrays-attribute"] { 59 | let arrayOfArrayOfOptionalBoolean: JSONType = 60 | .valueArray(isRequired: true, valueType: 61 | .valueArray(isRequired: true, valueType: 62 | .bool(isRequired: false))) 63 | XCTAssertEqual(type, arrayOfArrayOfOptionalBoolean) 64 | } 65 | else { XCTFail() } 66 | } 67 | 68 | func test_array_of_optional_array_of_optional_string() { 69 | let valuesArray = [ 70 | "string-arrays-attribute": [ 71 | NSNull(), 72 | ["A", "B"], 73 | ["C", "D", NSNull()], 74 | ] 75 | ] 76 | let schema = JSONElementSchema.inferred(from: valuesArray, named: dummyName) 77 | if let type = schema.attributes["string-arrays-attribute"] { 78 | let arrayOfOptionalArrayOfOptionalString: JSONType = 79 | .valueArray(isRequired: true, valueType: 80 | .valueArray(isRequired: false, valueType: 81 | .string(isRequired: false))) 82 | XCTAssertEqual(type, arrayOfOptionalArrayOfOptionalString) 83 | } 84 | else { XCTFail() } 85 | } 86 | 87 | func test_url_array_and_empty_array() { 88 | let jsonArray = [ 89 | [ 90 | "url-array-attribute": [ 91 | "http://fake-url.com" 92 | ] 93 | ], 94 | [ 95 | "url-array-attribute": [ 96 | // Empty array 97 | ] 98 | ] 99 | ] 100 | let schema = JSONElementSchema.inferred(from: jsonArray, named: dummyName) 101 | if let type = schema.attributes["url-array-attribute"] { 102 | let arrayOfRequiredURL: JSONType = .valueArray(isRequired: true, valueType: .url(isRequired: true)) 103 | XCTAssertEqual(type, arrayOfRequiredURL) 104 | } 105 | else { XCTFail() } 106 | } 107 | 108 | func test_mixed_array() { 109 | let jsonArray = [ 110 | "mixed-array-attribute": [ 111 | "Hello, World!", 112 | "http://fake-url.com", 113 | 42, 114 | NSNull(), 115 | 3.14, 116 | true 117 | ] 118 | ] 119 | let schema = JSONElementSchema.inferred(from: jsonArray, named: dummyName) 120 | if let type = schema.attributes["mixed-array-attribute"] { 121 | let arrayOfAnything: JSONType = .valueArray(isRequired: true, valueType: .anything) 122 | XCTAssertEqual(type, arrayOfAnything) 123 | } 124 | else { XCTFail() } 125 | } 126 | 127 | func test_mixed_array_and_empty_array() { 128 | let jsonArray = [ 129 | [ 130 | "mixed-array-attribute": [ 131 | "Hello, World!", 132 | "http://fake-url.com", 133 | 42, 134 | NSNull(), 135 | 3.14, 136 | true 137 | ] 138 | ], 139 | [ 140 | "mixed-array-attribute": [ 141 | // Empty array 142 | ] 143 | ], 144 | ] 145 | let schema = JSONElementSchema.inferred(from: jsonArray, named: dummyName) 146 | if let type = schema.attributes["mixed-array-attribute"] { 147 | let arrayOfAnything: JSONType = .valueArray(isRequired: true, valueType: .anything) 148 | XCTAssertEqual(type, arrayOfAnything) 149 | } 150 | else { XCTFail() } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /unit_tests/name-translation-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // name-translation-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/22/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class name_translation_tests: XCTestCase { 12 | 13 | // MARK: - Swift names from JSON names 14 | 15 | func test_to_swift_struct_name_unchanged_if_valid() { 16 | let jsonName = "MyStruct" 17 | XCTAssertEqual(jsonName.toSwiftStructName(), "MyStruct") 18 | } 19 | 20 | func test_to_swift_property_name_unchanged_if_valid() { 21 | let jsonName = "someProperty" 22 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "someProperty") 23 | } 24 | 25 | func test_to_swift_struct_name_removes_hyphen() { 26 | let jsonName = "fake-name" 27 | XCTAssertEqual(jsonName.toSwiftStructName(), "FakeName") 28 | } 29 | 30 | func test_to_swift_struct_name_removes_underscore() { 31 | let jsonName = "fake_name" 32 | XCTAssertEqual(jsonName.toSwiftStructName(), "FakeName") 33 | } 34 | 35 | func test_to_swift_property_name_removes_hyphen() { 36 | let jsonName = "fake-name" 37 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "fakeName") 38 | } 39 | 40 | func test_to_swift_property_name_removes_leading_underscore() { 41 | let jsonName = "_foo" 42 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "foo") 43 | } 44 | 45 | func test_to_swift_property_name_removes_trailing_underscore() { 46 | let jsonName = "foo_" 47 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "foo") 48 | } 49 | 50 | func test_to_swift_property_name_removes_separator_underscore() { 51 | let jsonName = "foo_bar" 52 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "fooBar") 53 | } 54 | 55 | func test_to_swift_property_name_removes_two_consecutive_separator_underscores() { 56 | let jsonName = "foo__bar" 57 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "fooBar") 58 | } 59 | 60 | func test_to_swift_property_name_preserves_trailing_caps() { 61 | let jsonName = "SomeHTML" 62 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "someHTML") 63 | } 64 | 65 | func test_to_swift_property_name_adds_underscore_before_initial_number() { 66 | let jsonName = "4th_item" 67 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "_4thItem") 68 | } 69 | 70 | // MARK: - Avoiding Swift keywords 71 | 72 | func test_to_swift_property_name_keyword_has_underscore_prefix() { 73 | let jsonName = "private" 74 | XCTAssertEqual(jsonName.toSwiftPropertyName(), "_private") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /unit_tests/swift-code-generation-custom-struct-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift-code-generation-custom-struct-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/21/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class swift_code_generation_custom_struct_tests: XCTestCase { 12 | func test_custom_struct() { 13 | let customStruct = createStruct(named: "SomeStruct") 14 | let transformation = TransformationFromJSON.toCustomStruct(attributeName: "a", propertyName: "p", type: customStruct) 15 | XCTAssertEqual(transformation.letStatement, "let p = SomeStruct(json: json, key: \"a\")") 16 | } 17 | 18 | func test_array_of_required_struct() { 19 | let someStruct = createStruct(named: "SomeStruct") 20 | let transformation = TransformationFromJSON.toCustomStructArray(attributeName: "a", propertyName: "p", elementType: someStruct, hasOptionalElements: false) 21 | XCTAssertEqual(transformation.letStatement, "let p = SomeStruct.createRequiredInstances(from: json, arrayKey: \"a\")") 22 | } 23 | 24 | func test_array_of_optional_struct() { 25 | let someStruct = createStruct(named: "SomeStruct") 26 | let transformation = TransformationFromJSON.toCustomStructArray(attributeName: "a", propertyName: "p", elementType: someStruct, hasOptionalElements: true) 27 | XCTAssertEqual(transformation.letStatement, "let p = SomeStruct.createOptionalInstances(from: json, arrayKey: \"a\")") 28 | } 29 | 30 | private func createStruct(named name: String) -> SwiftStruct { 31 | let initializer = SwiftInitializer(parameters: []) 32 | let failableInitializer = SwiftFailableInitializer(requiredTransformations: [], optionalTransformations: []) 33 | return SwiftStruct(name: name, 34 | properties: [], 35 | initializer: initializer, 36 | failableInitializer: failableInitializer, 37 | nestedStructs: []) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /unit_tests/swift-code-generation-primitive-array-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift-code-generation-primitive-array-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/21/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class swift_code_generation_primitive_array_tests: XCTestCase { 12 | 13 | // MARK: - Array with required primitive values 14 | 15 | func test_array_of_required_any() { 16 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .any, hasOptionalElements: false) 17 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? [Any?]") // Any is treated as optional. 18 | } 19 | 20 | func test_array_of_required_bool() { 21 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .bool, hasOptionalElements: false) 22 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? [Bool]") 23 | } 24 | 25 | func test_array_of_required_date() { 26 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .date(format: "M/d/yyyy"), hasOptionalElements: false) 27 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [String]).flatMap({ $0.toDateArray(withFormat: \"M/d/yyyy\") })") 28 | } 29 | 30 | func test_array_of_required_double() { 31 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .double, hasOptionalElements: false) 32 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [NSNumber]).map({ $0.toDoubleArray() })") 33 | } 34 | 35 | func test_array_of_required_emptyArray() { 36 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .emptyArray, hasOptionalElements: false) 37 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? [[Any?]]") 38 | } 39 | 40 | func test_array_of_required_int() { 41 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .int, hasOptionalElements: false) 42 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? [Int]") 43 | } 44 | 45 | func test_array_of_required_string() { 46 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .string, hasOptionalElements: false) 47 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? [String]") 48 | } 49 | 50 | func test_array_of_required_url() { 51 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .url, hasOptionalElements: false) 52 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [String]).flatMap({ $0.toURLArray() })") 53 | } 54 | 55 | // MARK: - Array with optional primitive values 56 | 57 | func test_array_of_optional_any() { 58 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .any, hasOptionalElements: true) 59 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalValueArray() as [Any?] })") 60 | } 61 | 62 | func test_array_of_optional_bool() { 63 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .bool, hasOptionalElements: true) 64 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalValueArray() as [Bool?] })") 65 | } 66 | 67 | func test_array_of_optional_date() { 68 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .date(format: "M/d/yyyy"), hasOptionalElements: true) 69 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalDateArray(withFormat: \"M/d/yyyy\") })") 70 | } 71 | 72 | func test_array_of_optional_double() { 73 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .double, hasOptionalElements: true) 74 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalDoubleArray() })") 75 | } 76 | 77 | func test_array_of_optional_emptyArray() { 78 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .emptyArray, hasOptionalElements: true) 79 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalValueArray() as [[Any?]?] })") 80 | } 81 | 82 | func test_array_of_optional_int() { 83 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .int, hasOptionalElements: true) 84 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalValueArray() as [Int?] })") 85 | } 86 | 87 | func test_array_of_optional_string() { 88 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .string, hasOptionalElements: true) 89 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalValueArray() as [String?] })") 90 | } 91 | 92 | func test_array_of_optional_url() { 93 | let transformation = TransformationFromJSON.toPrimitiveValueArray(attributeName: "a", propertyName: "p", elementType: .url, hasOptionalElements: true) 94 | XCTAssertEqual(transformation.letStatement, "let p = (json[\"a\"] as? [Any]).map({ $0.toOptionalURLArray() })") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /unit_tests/swift-code-generation-primitive-value-tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift-code-generation-primitive-value-tests.swift 3 | // json2swift 4 | // 5 | // Created by Joshua Smith on 10/21/16. 6 | // Copyright © 2016 iJoshSmith. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class swift_code_generation_primitive_value_tests: XCTestCase { 12 | func test_any() { 13 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .any) 14 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? Any") 15 | } 16 | 17 | func test_emptyArray() { 18 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .emptyArray) 19 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? [Any?]") 20 | } 21 | 22 | func test_bool() { 23 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .bool) 24 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? Bool") 25 | } 26 | 27 | func test_date() { 28 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .date(format: "M/d/yyyy")) 29 | XCTAssertEqual(transformation.letStatement, "let p = Date(json: json, key: \"a\", format: \"M/d/yyyy\")") 30 | } 31 | 32 | func test_double() { 33 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .double) 34 | XCTAssertEqual(transformation.letStatement, "let p = Double(json: json, key: \"a\")") 35 | } 36 | 37 | func test_int() { 38 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .int) 39 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? Int") 40 | } 41 | 42 | func test_string() { 43 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .string) 44 | XCTAssertEqual(transformation.letStatement, "let p = json[\"a\"] as? String") 45 | } 46 | 47 | func test_url() { 48 | let transformation = TransformationFromJSON.toPrimitiveValue(attributeName: "a", propertyName: "p", type: .url) 49 | XCTAssertEqual(transformation.letStatement, "let p = URL(json: json, key: \"a\")") 50 | } 51 | } 52 | --------------------------------------------------------------------------------