├── .github ├── FUNDING.yml └── workflows │ ├── Documentation.yml │ ├── Tests.yml │ ├── iOS.yml │ ├── tvOS.yml │ ├── visionOS.yml │ └── watchOS.yml ├── .gitignore ├── .mailmap ├── LICENSE ├── NOTICE ├── Package.resolved ├── Package.swift ├── README.md ├── Scripts └── TestAll ├── Snippets ├── DecodingArrays.swift ├── DecodingObjects.swift ├── DecodingWithCodable.swift ├── EncodingArrays.swift ├── Parsing.swift └── ParsingErrors.swift └── Sources ├── JSON ├── JSON (ext).swift ├── docs.docc │ ├── Decoding.md │ └── JSON.md └── exports.swift ├── JSONAST ├── JSON.Array.swift ├── JSON.EscapeCode.swift ├── JSON.IntegerOverflowError.swift ├── JSON.Key.swift ├── JSON.Literal.swift ├── JSON.Node.swift ├── JSON.Number.Base10.Inverse.swift ├── JSON.Number.Base10.swift ├── JSON.Number.swift ├── JSON.Object.swift ├── JSON.TypecastError.swift └── JSON.swift ├── JSONDecoding ├── Conformances │ ├── Array (ext).swift │ ├── Bool (ext).swift │ ├── Character (ext).swift │ ├── Dictionary (ext).swift │ ├── Double (ext).swift │ ├── Float (ext).swift │ ├── Float80 (ext).swift │ ├── Int (ext).swift │ ├── Int16 (ext).swift │ ├── Int32 (ext).swift │ ├── Int64 (ext).swift │ ├── Int8 (ext).swift │ ├── Set (ext).swift │ ├── String (ext).swift │ ├── UInt (ext).swift │ ├── UInt16 (ext).swift │ ├── UInt32 (ext).swift │ ├── UInt64 (ext).swift │ ├── UInt8 (ext).swift │ └── Unicode.Scalar (ext).swift ├── Decoding │ ├── JSON.ArrayShape.swift │ ├── JSON.ArrayShapeCriteria.swift │ ├── JSON.ArrayShapeError.swift │ ├── JSON.DecodingError.swift │ ├── JSON.ObjectDecoder.swift │ ├── JSON.ObjectKeyError.swift │ ├── JSON.SingleKeyError.swift │ └── JSON.ValueError.swift ├── Fields │ ├── JSON.FieldDecoder.swift │ ├── JSON.OptionalDecoder.swift │ └── JSON.TraceableDecoder.swift ├── JSON.Array (ext).swift ├── JSON.Object (ext).swift ├── JSONDecodable.swift ├── JSONObjectDecodable.swift ├── JSONStringDecodable.swift ├── Never (ext).swift ├── Optional (ext).swift └── exports.swift ├── JSONEncoding ├── Conformances │ ├── Array (ext).swift │ ├── ArraySlice (ext).swift │ ├── Bool (ext).swift │ ├── Character (ext).swift │ ├── Int (ext).swift │ ├── Int16 (ext).swift │ ├── Int32 (ext).swift │ ├── Int64 (ext).swift │ ├── Int8 (ext).swift │ ├── StaticString (ext).swift │ ├── String (ext).swift │ ├── Substring (ext).swift │ ├── UInt (ext).swift │ ├── UInt16 (ext).swift │ ├── UInt32 (ext).swift │ ├── UInt64 (ext).swift │ ├── UInt8 (ext).swift │ └── Unicode.Scalar (ext).swift ├── Encoders │ ├── JSON.ArrayEncoder.Index.swift │ ├── JSON.ArrayEncoder.swift │ ├── JSON.InlineEncoder.swift │ ├── JSON.Literal (ext).swift │ └── JSON.ObjectEncoder.swift ├── JSON (ext).swift ├── JSONEncodable.swift ├── JSONObjectEncodable.swift ├── JSONStringEncodable.swift ├── Never (ext).swift ├── Optional (ext).swift └── exports.swift ├── JSONLegacy ├── CodableCompatibility │ ├── JSON.KeyedDecoder.Super.swift │ ├── JSON.KeyedDecoder.swift │ ├── JSON.Node (ext).swift │ ├── JSON.SingleValueDecoder.swift │ ├── JSON.UnkeyedDecoder.Index.swift │ └── JSON.UnkeyedDecoder.swift ├── DecodingError (ext).swift └── exports.swift ├── JSONParsing ├── JSON.Array (ext).swift ├── JSON.InvalidUnicodeScalarError.swift ├── JSON.Node (ext).swift ├── JSON.Object (ext).swift ├── Rules │ ├── JSON (ext).swift │ ├── JSON.NodeRule.Array.swift │ ├── JSON.NodeRule.False.swift │ ├── JSON.NodeRule.Null.swift │ ├── JSON.NodeRule.Object.Item.swift │ ├── JSON.NodeRule.Object.swift │ ├── JSON.NodeRule.True.swift │ ├── JSON.NodeRule.swift │ ├── JSON.NumberRule.PlusOrMinus.swift │ ├── JSON.NumberRule.swift │ ├── JSON.RootRule.swift │ ├── JSON.StringRule.CodeUnit.swift │ ├── JSON.StringRule.EscapeSequence.swift │ ├── JSON.StringRule.EscapedCodeUnit.swift │ ├── JSON.StringRule.swift │ └── JSON.WhitespaceRule.swift └── exports.swift └── JSONTests ├── IntegerOverflow.swift └── Parsing.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tayloraswift] 2 | -------------------------------------------------------------------------------- /.github/workflows/Documentation.yml: -------------------------------------------------------------------------------- 1 | # This workflow validates the package’s documentation. Because documentation building involves 2 | # compiling the package, this also checks that the package itself compiles successfully on each 3 | # supported platform. 4 | name: documentation 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | linux: 14 | runs-on: ubuntu-24.04 15 | name: Ubuntu 24.04 16 | 17 | steps: 18 | - name: Install Swift 19 | uses: tayloraswift/swift-install-action@master 20 | with: 21 | swift-prefix: "swift-6.0.3-release/ubuntu2404/swift-6.0.3-RELEASE" 22 | swift-id: "swift-6.0.3-RELEASE-ubuntu24.04" 23 | 24 | - name: Install Unidoc 25 | uses: tayloraswift/swift-unidoc-action@master 26 | 27 | # This clobbers everything in the current directory! 28 | - name: Checkout repository 29 | uses: actions/checkout@v3 30 | 31 | - name: Validate documentation 32 | run: | 33 | unidoc compile \ 34 | --swift-toolchain $SWIFT_INSTALLATION \ 35 | --ci fail-on-errors \ 36 | --project-path . 37 | 38 | macos: 39 | runs-on: macos-15 40 | name: macOS 41 | steps: 42 | - name: Install Unidoc 43 | uses: tayloraswift/swift-unidoc-action@master 44 | 45 | - name: Checkout repository 46 | uses: actions/checkout@v3 47 | 48 | - name: Validate documentation 49 | run: | 50 | unidoc compile \ 51 | --ci fail-on-errors \ 52 | --project-path . 53 | -------------------------------------------------------------------------------- /.github/workflows/Tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-24.04 12 | name: Ubuntu 24.04 13 | steps: 14 | - name: Install Swift 15 | uses: tayloraswift/swift-install-action@master 16 | with: 17 | swift-prefix: "swift-6.0.3-release/ubuntu2404/swift-6.0.3-RELEASE" 18 | swift-id: "swift-6.0.3-RELEASE-ubuntu24.04" 19 | 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | 23 | - name: Run tests 24 | run: Scripts/TestAll 25 | 26 | macos: 27 | runs-on: macos-15 28 | name: macOS 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v3 32 | 33 | - name: Run tests 34 | run: Scripts/TestAll 35 | -------------------------------------------------------------------------------- /.github/workflows/iOS.yml: -------------------------------------------------------------------------------- 1 | name: iOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-json-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.github/workflows/tvOS.yml: -------------------------------------------------------------------------------- 1 | name: tvOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-json-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.github/workflows/visionOS.yml: -------------------------------------------------------------------------------- 1 | name: visionOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-json-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.github/workflows/watchOS.yml: -------------------------------------------------------------------------------- 1 | name: watchOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-json-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.build 2 | /.build.* 3 | /.ssgc 4 | /.vscode 5 | /.swift-version 6 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Dianna 2 | Dianna 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2024 Dianna Ma (@taylorswift, @tayloraswift) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | the `swift-json` package was created by dianna ma (@taylorswift). 2 | 3 | you are welcome to contribute to this project at: 4 | 5 | https://github.com/tayloraswift/swift-json 6 | 7 | we do not maintain any other mirrors of this repository, and such 8 | forks of this repository may not carry the most up-to-date code. 9 | 10 | contributors: 11 | 12 | 1. dianna ma (@taylorswift, 2021–22) 13 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "35cbb9f0ead3c79119f3802da30b23934a23684e0f0ff19bd4fab5d106f27af0", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-grammar", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/tayloraswift/swift-grammar", 8 | "state" : { 9 | "revision" : "0dac977b50bf677b2c3adabd7d5586a7b6e09b17", 10 | "version" : "0.5.0" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | import PackageDescription 3 | 4 | let package:Package = .init( 5 | name: "swift-json", 6 | platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)], 7 | products: [ 8 | .library(name: "JSON", targets: ["JSON"]), 9 | .library(name: "JSONAST", targets: ["JSONAST"]), 10 | .library(name: "JSONLegacy", targets: ["JSONLegacy"]), 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/tayloraswift/swift-grammar", from: "0.5.0"), 14 | ], 15 | targets: [ 16 | .target(name: "JSONAST"), 17 | 18 | .target(name: "JSONDecoding", 19 | dependencies: [ 20 | .target(name: "JSONAST"), 21 | .product(name: "Grammar", package: "swift-grammar"), 22 | ]), 23 | 24 | .target(name: "JSONEncoding", 25 | dependencies: [ 26 | .target(name: "JSONAST"), 27 | ]), 28 | 29 | .target(name: "JSONLegacy", 30 | dependencies: [ 31 | .target(name: "JSONDecoding"), 32 | ]), 33 | 34 | .target(name: "JSONParsing", 35 | dependencies: [ 36 | .target(name: "JSONAST"), 37 | .product(name: "Grammar", package: "swift-grammar"), 38 | ]), 39 | 40 | .target(name: "JSON", 41 | dependencies: [ 42 | .target(name: "JSONDecoding"), 43 | .target(name: "JSONEncoding"), 44 | .target(name: "JSONParsing"), 45 | ]), 46 | 47 | .testTarget(name: "JSONTests", 48 | dependencies: [ 49 | .target(name: "JSON"), 50 | ]), 51 | ] 52 | ) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ***`json`*** 4 | 5 | [![Tests](https://github.com/tayloraswift/swift-json/actions/workflows/Tests.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/Tests.yml) 6 | [![Documentation](https://github.com/tayloraswift/swift-json/actions/workflows/Documentation.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/Documentation.yml) 7 | 8 |
9 | 10 | A pure Swift JSON parsing and encoding library designed for high-performance, high-throughput server-side applications. 11 | 12 | 13 |
14 | 15 | [documentation and api reference](https://swiftinit.org/docs/swift-json/json) 16 | 17 |
18 | 19 | 20 | ## Requirements 21 | 22 | The swift-json library requires Swift 5.9 or later. 23 | 24 | 25 | | Platform | Status | 26 | | -------- | ------ | 27 | | 🐧 Linux | [![Tests](https://github.com/tayloraswift/swift-json/actions/workflows/Tests.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/Tests.yml) | 28 | | 🍏 Darwin | [![Tests](https://github.com/tayloraswift/swift-json/actions/workflows/Tests.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/Tests.yml) | 29 | | 🍏 Darwin (iOS) | [![iOS](https://github.com/tayloraswift/swift-json/actions/workflows/iOS.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/iOS.yml) | 30 | | 🍏 Darwin (tvOS) | [![tvOS](https://github.com/tayloraswift/swift-json/actions/workflows/tvOS.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/tvOS.yml) | 31 | | 🍏 Darwin (visionOS) | [![visionOS](https://github.com/tayloraswift/swift-json/actions/workflows/visionOS.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/visionOS.yml) | 32 | | 🍏 Darwin (watchOS) | [![watchOS](https://github.com/tayloraswift/swift-json/actions/workflows/watchOS.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/watchOS.yml) | 33 | 34 | 35 | [Check deployment minimums](https://swiftinit.org/docs/swift-json#ss:platform-requirements) 36 | -------------------------------------------------------------------------------- /Scripts/TestAll: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | swift --version 4 | swift test 5 | swift build -c release 6 | -------------------------------------------------------------------------------- /Snippets/DecodingArrays.swift: -------------------------------------------------------------------------------- 1 | import JSON 2 | 3 | // snippet.POINT 4 | struct Point 5 | { 6 | let x:Double 7 | let y:Double 8 | } 9 | // snippet.POINT_DECODE 10 | extension Point:JSONDecodable 11 | { 12 | init(json:JSON.Node) throws 13 | { 14 | let array:JSON.Array = try .init(json: json) 15 | try array.shape.expect(count: 2) 16 | self.init(x: try array[0].decode(), y: try array[1].decode()) 17 | } 18 | } 19 | // snippet.MAIN 20 | func decode(message:String) throws -> [(Point, Point, Point)] 21 | { 22 | // snippet.MAIN_PARSE 23 | let json:JSON.Array = try .init(parsing: message) 24 | // snippet.MAIN_CHECK_SHAPE 25 | try json.shape.expect(multipleOf: 3) 26 | // snippet.MAIN_DECODE 27 | return try stride(from: json.startIndex, to: json.endIndex, by: 3).map 28 | { 29 | ( 30 | try json[$0 ].decode(to: Point.self), 31 | try json[$0 + 1].decode(to: Point.self), 32 | try json[$0 + 2].decode(to: Point.self) 33 | ) 34 | } 35 | // snippet.end 36 | } 37 | // snippet.MAIN_CALL 38 | print(try decode(message: "[[0, 0], [0, 1], [1, 0]]")) 39 | -------------------------------------------------------------------------------- /Snippets/DecodingObjects.swift: -------------------------------------------------------------------------------- 1 | import JSON 2 | 3 | // snippet.MARKETTYPE_ENUM 4 | enum MarketType:String 5 | { 6 | case spot 7 | case future 8 | } 9 | // snippet.MARKETTYPE_DECODABLE 10 | extension MarketType:JSONDecodable 11 | { 12 | } 13 | // snippet.MARKET 14 | struct Market 15 | { 16 | let name:String 17 | let type:MarketType 18 | let isPerpetual:Bool 19 | 20 | private 21 | init(name:String, type:MarketType, isPerpetual:Bool) 22 | { 23 | self.name = name 24 | self.type = type 25 | self.isPerpetual = isPerpetual 26 | } 27 | } 28 | // snippet.MARKET_CODING_KEY 29 | extension Market 30 | { 31 | enum CodingKey:String 32 | { 33 | case name 34 | case type 35 | case perpetual 36 | } 37 | } 38 | // snippet.MARKET_DECODE 39 | extension Market:JSONObjectDecodable 40 | { 41 | init(json:JSON.ObjectDecoder) throws 42 | { 43 | self.init( 44 | name: try json[.name].decode(), 45 | type: try json[.type].decode(), 46 | isPerpetual: try json[.perpetual]?.decode() ?? false) 47 | } 48 | } 49 | // snippet.MAIN 50 | func decode(message:String) throws -> Market 51 | { 52 | // snippet.MAIN_PARSE_AND_INDEX 53 | let object:JSON.Object = try .init(parsing: message) 54 | let json:JSON.ObjectDecoder = try .init(indexing: object) 55 | 56 | // snippet.MAIN_DECODE 57 | return try json["market"].decode() 58 | // snippet.end 59 | } 60 | // snippet.MAIN_CALL 61 | print(try decode(message: """ 62 | { 63 | "market": { 64 | "name": "BTC-PERP", 65 | "type": "future", 66 | "perpetual": true 67 | } 68 | } 69 | """)) 70 | 71 | print(try decode(message: """ 72 | { 73 | "market": { 74 | "name": "BTC-PERP", 75 | "type": "spot" 76 | } 77 | } 78 | """)) 79 | -------------------------------------------------------------------------------- /Snippets/DecodingWithCodable.swift: -------------------------------------------------------------------------------- 1 | import JSON 2 | import JSONLegacy 3 | 4 | struct Decimal:Codable 5 | { 6 | let units:Int 7 | let places:Int 8 | } 9 | struct Response:Codable 10 | { 11 | let success:Bool 12 | let value:Decimal 13 | } 14 | 15 | let string:String = """ 16 | {"success":true,"value":0.1} 17 | """ 18 | let decoder:JSON.Node = try .init(parsing: string) 19 | let response:Response = try .init(from: decoder) 20 | 21 | print(response) 22 | -------------------------------------------------------------------------------- /Snippets/EncodingArrays.swift: -------------------------------------------------------------------------------- 1 | import JSON 2 | 3 | let json:JSON = .array 4 | { 5 | for i:Int in 0 ... 3 6 | { 7 | $0[+] = [_].init(0 ... i) 8 | } 9 | } 10 | 11 | print(json) 12 | -------------------------------------------------------------------------------- /Snippets/Parsing.swift: -------------------------------------------------------------------------------- 1 | import JSON 2 | 3 | let string:String = """ 4 | {"success": true, "value": 0.1} 5 | """ 6 | 7 | let json:JSON.Node = try .init(parsing: string) 8 | 9 | print(json) 10 | -------------------------------------------------------------------------------- /Snippets/ParsingErrors.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | import JSON 3 | 4 | let invalid:String = """ 5 | {"success":true,value:0.1} 6 | """ 7 | do 8 | { 9 | let _:JSON.Node = try .init(parsing: invalid) 10 | } 11 | catch is Pattern.UnexpectedValueError 12 | { 13 | print("JSON failed to parse!") 14 | } 15 | -------------------------------------------------------------------------------- /Sources/JSON/JSON (ext).swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// Parses and decodes this raw JSON string as an instance of `Decodable`. 4 | @inlinable public 5 | func decode(_ type:Decodable.Type = Decodable.self) throws -> Decodable 6 | where Decodable:JSONDecodable 7 | { 8 | try .init(json: try JSON.Node.init(parsing: self)) 9 | } 10 | 11 | /// Creates a raw JSON string by encoding the given instance of `Encodable`. 12 | @inlinable public static 13 | func encode(_ value:Encodable) -> Self 14 | where Encodable:JSONEncodable 15 | { 16 | var json:Self = .init(utf8: []) 17 | value.encode(to: &json) 18 | return json 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/JSON/docs.docc/Decoding.md: -------------------------------------------------------------------------------- 1 | # Decoding JSON 2 | 3 | Projects that use a lot of JSON often benefit from a protocol-oriented code organization strategy. This tutorial will show you how to decode schema using the ``JSONDecodable`` protocol hierarchy. 4 | 5 | 6 | ## Why a parallel protocol hierarchy? 7 | 8 | There are two main reasons why ``JSONDecodable`` (and ``JSONEncodable``) are separate from ``Codable``. The first reason is performance. Due to inherent limitations of ``Codable``, many users gain a considerable performance boost by using the more-specialized JSON protocols. 9 | 10 | Another reason is code organization. You may find protocol conformances to be a natural place to attach decoding logic to, and the ``JSONDecodable`` hierarchy helps you answer the all-important question of “where should my code live?”. 11 | 12 | 13 | ## Topics 14 | 15 | ### Decodability protocols 16 | 17 | Most decodable types have a particular type of AST ``JSON/Node`` that they are decoded from. Although the ``JSONDecodable.init(json:) [requirement]`` requirement is the ultimate witness that will be called by the decoder, it is often more convenient to decode from a ``String`` or a typed ``JSON.ObjectDecoder``. 18 | 19 | - ``JSONDecodable`` 20 | - ``JSONStringDecodable`` 21 | - ``JSONObjectDecodable`` 22 | 23 | 24 | ### Decoding strings 25 | 26 | The ``JSONStringDecodable`` protocol is a bridge that allows string-convertible types to opt into an automatic string-based ``JSONDecodable`` conformance. If your type already conforms to ``LosslessStringConvertible``, you do not need to write any code other than the conformance itself. 27 | 28 | 29 | ### Decoding objects 30 | 31 | Distinguishing between missing, null, and present values is a common problem when decoding JSON objects. Generally, you access some ``JSON/TraceableDecoder`` through a (non-throwing) subscript, and then catch errors when calling one of its `decode` methods. This allows you to express varying expectations using optional chaining. 32 | 33 | | Syntax | Meaning | 34 | | --- | --- | 35 | | `try $0[k]?.decode()` | The field is optional, and throw an error if it is present but not decodable. | 36 | | `try $0[k].decode()` | The field is required, and throw an error if it is missing or not decodable. | 37 | 38 | The field decoder types hold a temporary copy of the key which they are decoding, which allows them to display a helpful stack trace if decoding fails. 39 | 40 | - ``JSON.FieldDecoder`` 41 | - ``JSON.OptionalDecoder`` 42 | 43 | 44 | #### Worked example 45 | 46 | Here’s an example of decoding a JSON object. 47 | 48 | @Snippet(id: DecodingObjects) 49 | 50 | 51 | ### Decoding arrays 52 | 53 | Decoding arrays involves a similar syntax to decoding objects, but there is no concept of an optional field. Instead, you generally check preconditions on an``JSON/ArrayShape`` before proceeding with decoding. 54 | 55 | #### Worked example 56 | 57 | Here’s an example of decoding a JSON array. 58 | 59 | @Snippet(id: DecodingArrays) 60 | -------------------------------------------------------------------------------- /Sources/JSON/docs.docc/JSON.md: -------------------------------------------------------------------------------- 1 | # ``/JSON`` 2 | 3 | Swift JSON is a Foundation-free JSON parser and encoder written in pure Swift. It is designed to be performant, expressive, and speedy to compile. 4 | 5 | 6 | ## Getting started 7 | 8 | Many users only need to parse simple JSON messages, which you can do with the ``JSON.Node.init(parsing:) (String)`` initializer: 9 | 10 | @Snippet(id: Parsing) 11 | 12 | This produces a JSON AST ``JSON/Node``. If you import the ``JSONLegacy`` module, this type conforms to ``Decoder``, so you can decode any type that conforms to ``Decodable``. 13 | 14 | @Snippet(id: DecodingWithCodable) 15 | 16 | For more advanced use cases, we suggest reading the library tutorials. 17 | 18 | ## Topics 19 | 20 | ### Tutorials 21 | 22 | - 23 | 24 | 25 | ### Exported modules 26 | 27 | This module re-exports several other modules. We have invested significant effort into making these four modules as lightweight and fast to compile as possible, so we recommend importing `JSON` as a whole unless you have a measurable problem. Notably, the `JSON` module does not include the ``JSONLegacy`` module, which is designed to provide backwards compatibility with codebases that rely on ``Codable`` for JSON serialization. 28 | 29 | - ``JSONAST`` 30 | - ``JSONParsing`` 31 | - ``JSONDecoding`` 32 | - ``JSONEncoding`` 33 | 34 | 35 | ### AST 36 | 37 | - ``JSON.Node`` 38 | - ``JSON.Array`` 39 | - ``JSON.Object`` 40 | - ``JSON.Number`` 41 | 42 | ### Raw JSON 43 | 44 | - ``JSON`` 45 | -------------------------------------------------------------------------------- /Sources/JSON/exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import JSONAST 2 | @_exported import JSONDecoding 3 | @_exported import JSONEncoding 4 | @_exported import JSONParsing 5 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Array.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A JSON array, which can recursively contain instances of ``JSON``. 4 | /// This type is a transparent wrapper around a native [`[JSON]`]() 5 | /// array. 6 | @frozen public 7 | struct Array 8 | { 9 | public 10 | var elements:[JSON.Node] 11 | 12 | @inlinable public 13 | init(_ elements:[JSON.Node] = []) 14 | { 15 | self.elements = elements 16 | } 17 | } 18 | } 19 | extension JSON.Array:CustomStringConvertible 20 | { 21 | /// Returns this array serialized as a minified string. 22 | /// 23 | /// Reparsing and reserializing this string is guaranteed to return the 24 | /// same string. 25 | public 26 | var description:String 27 | { 28 | "[\(self.elements.map(\.description).joined(separator: ","))]" 29 | } 30 | } 31 | extension JSON.Array:ExpressibleByArrayLiteral 32 | { 33 | @inlinable public 34 | init(arrayLiteral:JSON.Node...) 35 | { 36 | self.init(arrayLiteral) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.EscapeCode.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | @frozen @usableFromInline 4 | enum EscapeCode:Equatable, Hashable, Comparable, Sendable 5 | { 6 | case b 7 | case t 8 | case n 9 | case f 10 | case r 11 | case quote 12 | case backslash 13 | } 14 | } 15 | extension JSON.EscapeCode 16 | { 17 | @inlinable 18 | init?(escaping codeunit:UInt8) 19 | { 20 | switch codeunit 21 | { 22 | case 0x08: self = .b 23 | case 0x09: self = .t 24 | case 0x0A: self = .n 25 | case 0x0C: self = .f 26 | case 0x0D: self = .r 27 | case 0x22: self = .quote 28 | case 0x5C: self = .backslash 29 | default: return nil 30 | } 31 | } 32 | 33 | @inlinable static 34 | func += (utf8:inout ArraySlice, self:Self) 35 | { 36 | utf8.append(0x5C) // '\' 37 | switch self 38 | { 39 | case .b: utf8.append(0x62) // 'b' 40 | case .t: utf8.append(0x74) // 't' 41 | case .n: utf8.append(0x6E) // 'n' 42 | case .f: utf8.append(0x66) // 'f' 43 | case .r: utf8.append(0x72) // 'r' 44 | case .quote: utf8.append(0x22) // '"' 45 | case .backslash:utf8.append(0x5C) // '\' 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.IntegerOverflowError.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// An integer overflow occurred while converting a number literal to a desired type. 4 | /// 5 | /// This error is thrown by decoders, and is different from 6 | /// `Grammar.Pattern.IntegerOverflowError`, which is thrown by the parser. 7 | public 8 | struct IntegerOverflowError:Error, Sendable 9 | { 10 | /// The number literal that could not be converted. 11 | public 12 | let number:Number 13 | 14 | /// The metatype of the desired integer type. 15 | public 16 | let overflows:any FixedWidthInteger.Type 17 | 18 | public 19 | init(number:Number, overflows:any FixedWidthInteger.Type) 20 | { 21 | self.number = number 22 | self.overflows = overflows 23 | } 24 | } 25 | } 26 | extension JSON.IntegerOverflowError 27 | { 28 | @available(swift, deprecated: 5.7, 29 | message: "use the more strongly-typed 'overflows' property") 30 | public 31 | var type:Any.Type { self.overflows } 32 | } 33 | extension JSON.IntegerOverflowError:Equatable 34 | { 35 | public static 36 | func == (lhs:Self, rhs:Self) -> Bool 37 | { 38 | lhs.equals(number: rhs.number, overflows: rhs.overflows) 39 | } 40 | 41 | private 42 | func equals(number:JSON.Number, overflows _:Integer.Type) -> Bool 43 | where Integer:FixedWidthInteger 44 | { 45 | self.number == number && self.overflows is Integer.Type 46 | } 47 | } 48 | extension JSON.IntegerOverflowError:CustomStringConvertible 49 | { 50 | public 51 | var description:String 52 | { 53 | "integer literal '\(number)' overflows decoded type '\(self.overflows)'" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Key.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | @frozen public 4 | struct Key:Hashable, RawRepresentable, Sendable 5 | { 6 | public 7 | let rawValue:String 8 | 9 | @inlinable public 10 | init(rawValue:String) 11 | { 12 | self.rawValue = rawValue 13 | } 14 | } 15 | } 16 | extension JSON.Key 17 | { 18 | @inlinable public 19 | init(_ other:some RawRepresentable) 20 | { 21 | self.init(rawValue: other.rawValue) 22 | } 23 | public 24 | init(_ codingKey:some CodingKey) 25 | { 26 | self.init(rawValue: codingKey.stringValue) 27 | } 28 | } 29 | extension JSON.Key:CustomStringConvertible 30 | { 31 | @inlinable public 32 | var description:String 33 | { 34 | self.rawValue 35 | } 36 | } 37 | extension JSON.Key:ExpressibleByStringLiteral 38 | { 39 | @inlinable public 40 | init(stringLiteral:String) 41 | { 42 | self.init(rawValue: stringLiteral) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Literal.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | @frozen public 4 | struct Literal 5 | { 6 | public 7 | var value:Value 8 | 9 | @inlinable public 10 | init(_ value:Value) 11 | { 12 | self.value = value 13 | } 14 | } 15 | } 16 | extension JSON.Literal:Sendable where Value:Sendable 17 | { 18 | } 19 | extension JSON.Literal:Equatable where Value:Equatable 20 | { 21 | } 22 | extension JSON.Literal:Hashable where Value:Hashable 23 | { 24 | } 25 | extension JSON.Literal where Value:StringProtocol 26 | { 27 | /// Encodes this literal’s string ``value``, with surrounding quotes, to the provided JSON 28 | /// stream. This function escapes any special characters in the string. 29 | @inlinable public static 30 | func += (json:inout JSON, self:Self) 31 | { 32 | json.utf8.append(0x22) // '"' 33 | for codeunit:UInt8 in self.value.utf8 34 | { 35 | if let code:JSON.EscapeCode = .init(escaping: codeunit) 36 | { 37 | json.utf8 += code 38 | } 39 | else 40 | { 41 | json.utf8.append(codeunit) 42 | } 43 | } 44 | json.utf8.append(0x22) // '"' 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Node.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | @frozen public 4 | enum Node:Sendable 5 | { 6 | /// A null value. 7 | /// 8 | /// The library models statically-typed null values elsewhere as `Optional`. 9 | case null 10 | /// A boolean value. 11 | case bool(Bool) 12 | /// A string value. 13 | /// 14 | /// The contents of this string are *not* escaped. 15 | case string(Literal) 16 | /// A numerical value. 17 | case number(Number) 18 | 19 | /// An array container. 20 | case array(Array) 21 | /// An object container. 22 | case object(Object) 23 | } 24 | } 25 | 26 | extension String 27 | { 28 | init(_ literal:JSON.Literal) 29 | { 30 | var json:JSON = .init(utf8: []) 31 | 32 | json.utf8.reserveCapacity(literal.value.utf8.count + 2) 33 | json += literal 34 | 35 | self.init(decoding: json.utf8, as: Unicode.UTF8.self) 36 | } 37 | } 38 | 39 | extension JSON.Node 40 | { 41 | // TODO: optimize this, it should operate at the utf8 level, and be @inlinable 42 | 43 | /// Escapes and formats a string as a JSON string literal, including the 44 | /// beginning and ending quote characters. This function is used for debug reflection only; 45 | /// it is less efficient than the UTF-8 escaping implementation used by the encoder. 46 | /// 47 | /// - Parameters: 48 | /// - string: A string to escape. 49 | /// - Returns: A string literal, which includes the [`""`]() delimiters. 50 | /// 51 | /// This function escapes the following characters: `"`, `\`, `\b`, `\t`, `\n`, 52 | /// `\f`, and `\r`. It does not escape forward slashes (`/`). 53 | /// 54 | /// JSON string literals may contain unicode characters, even after escaping. 55 | /// Do not assume the output of this function is ASCII. 56 | /// 57 | /// > Important: This function should *not* be called on an input to the ``string(_:)`` 58 | /// case constructor. The library performs string escaping lazily; calling this 59 | /// function explicitly will double-escape the input. 60 | // static 61 | // func escape(_ string:S) -> String where S:StringProtocol 62 | // { 63 | // var escaped:String = "\"" 64 | // for character:Character in string 65 | // { 66 | // switch character 67 | // { 68 | // case "\"": escaped += "\\\"" 69 | // case "\\": escaped += "\\\\" 70 | // // slash escape is not mandatory, and does not improve legibility 71 | // // case "/": escaped += "\\/" 72 | // case "\u{08}": escaped += "\\b" 73 | // case "\u{09}": escaped += "\\t" 74 | // case "\u{0A}": escaped += "\\n" 75 | // case "\u{0C}": escaped += "\\f" 76 | // case "\u{0D}": escaped += "\\r" 77 | // default: escaped.append(character) 78 | // } 79 | // } 80 | // escaped += "\"" 81 | // return escaped 82 | // } 83 | } 84 | extension JSON.Node:CustomStringConvertible 85 | { 86 | /// Returns this value serialized as a minified string. 87 | /// 88 | /// Reparsing and reserializing this string is guaranteed to return the 89 | /// same string. 90 | public 91 | var description:String 92 | { 93 | switch self 94 | { 95 | case .null: "null" 96 | case .bool(true): "true" 97 | case .bool(false): "false" 98 | case .string(let literal): .init(literal) 99 | case .number(let value): value.description 100 | case .array(let array): array.description 101 | case .object(let object): object.description 102 | } 103 | } 104 | } 105 | extension JSON.Node:ExpressibleByDictionaryLiteral 106 | { 107 | @inlinable public 108 | init(dictionaryLiteral:(JSON.Key, Self)...) 109 | { 110 | self = .object(.init(dictionaryLiteral)) 111 | } 112 | } 113 | extension JSON.Node:ExpressibleByArrayLiteral 114 | { 115 | @inlinable public 116 | init(arrayLiteral:Self...) 117 | { 118 | self = .array(.init(arrayLiteral)) 119 | } 120 | } 121 | extension JSON.Node:ExpressibleByStringLiteral 122 | { 123 | @inlinable public 124 | init(stringLiteral:String) 125 | { 126 | self = .string(JSON.Literal.init(stringLiteral)) 127 | } 128 | } 129 | extension JSON.Node:ExpressibleByBooleanLiteral 130 | { 131 | @inlinable public 132 | init(booleanLiteral:Bool) 133 | { 134 | self = .bool(booleanLiteral) 135 | } 136 | } 137 | 138 | extension JSON.Node 139 | { 140 | /// Promotes a `nil` result to a thrown ``TypecastError``. 141 | /// 142 | /// If `T` conforms to ``JSONDecodable``, prefer calling its throwing 143 | /// ``JSONDecodable/init(json:) [requirement]`` to calling this method directly. 144 | /// 145 | /// > Throws: 146 | /// A ``TypecastError`` if the given closure returns nil. 147 | /// 148 | /// > Complexity: O(1), as long as the closure is O(1). 149 | @inline(__always) 150 | @inlinable public 151 | func cast(with cast:(Self) throws -> T?) throws -> T 152 | { 153 | if let value:T = try cast(self) 154 | { 155 | return value 156 | } 157 | else 158 | { 159 | throw JSON.TypecastError.init(invalid: self) 160 | } 161 | } 162 | } 163 | 164 | extension JSON.Node 165 | { 166 | /// Attempts to load an instance of ``Bool`` from this variant. 167 | /// 168 | /// - Returns: 169 | /// The payload of this variant if it matches ``bool(_:) [case]``, 170 | /// nil otherwise. 171 | /// 172 | /// > Complexity: O(1). 173 | @inlinable public 174 | func `as`(_:Bool.Type) -> Bool? 175 | { 176 | switch self 177 | { 178 | case .bool(let bool): bool 179 | default: nil 180 | } 181 | } 182 | 183 | /// Attempts to load an instance of some ``SignedInteger`` from this variant. 184 | /// 185 | /// - Returns: A signed integer derived from the payload of this variant 186 | /// if it matches ``number(_:) [case]``, and it can be represented exactly 187 | /// by `T`; `nil` otherwise. 188 | /// 189 | /// This method reports failure in two ways — it returns `nil` on a type 190 | /// mismatch, and it throws an ``IntegerOverflowError`` if this variant 191 | /// matches ``number(_:) [case]``, but it could not be represented exactly by `T`. 192 | /// 193 | /// > Note: 194 | /// This type conversion will fail if ``Number.places`` is non-zero, even if 195 | /// the fractional part is zero. For example, you can convert `5` to an 196 | /// integer, but not `5.0`. This matches the behavior of 197 | /// ``ExpressibleByIntegerLiteral``. 198 | /// 199 | /// > Complexity: O(1). 200 | @inlinable public 201 | func `as`(_:Integer.Type) throws -> Integer? 202 | where Integer:FixedWidthInteger & SignedInteger 203 | { 204 | // do not use init(exactly:) with decimal value directly, as this 205 | // will also accept values like 1.0, which we want to reject 206 | guard case .number(let number) = self 207 | else 208 | { 209 | return nil 210 | } 211 | guard let integer:Integer = number.as(Integer.self) 212 | else 213 | { 214 | throw JSON.IntegerOverflowError.init(number: number, overflows: Integer.self) 215 | } 216 | return integer 217 | } 218 | /// Attempts to load an instance of some ``UnsignedInteger`` from this variant. 219 | /// 220 | /// - Returns: An unsigned integer derived from the payload of this variant 221 | /// if it matches ``number(_:) [case]``, and it can be represented exactly 222 | /// by `T`; `nil` otherwise. 223 | /// 224 | /// This method reports failure in two ways — it returns `nil` on a type 225 | /// mismatch, and it throws an ``IntegerOverflowError`` if this variant 226 | /// matches ``number(_:) [case]``, but it could not be represented exactly by `T`. 227 | /// 228 | /// > Note: 229 | /// This type conversion will fail if ``Number.places`` is non-zero, even if 230 | /// the fractional part is zero. For example, you can convert `5` to an 231 | /// integer, but not `5.0`. This matches the behavior of 232 | /// ``ExpressibleByIntegerLiteral``. 233 | /// 234 | /// > Complexity: O(1). 235 | @inlinable public 236 | func `as`(_:Integer.Type) throws -> Integer? 237 | where Integer:FixedWidthInteger & UnsignedInteger 238 | { 239 | guard case .number(let number) = self 240 | else 241 | { 242 | return nil 243 | } 244 | guard let integer:Integer = number.as(Integer.self) 245 | else 246 | { 247 | throw JSON.IntegerOverflowError.init(number: number, overflows: Integer.self) 248 | } 249 | return integer 250 | } 251 | #if (os(Linux) || os(macOS)) && arch(x86_64) 252 | /// Attempts to load an instance of ``Float80`` from this variant. 253 | /// 254 | /// - Returns: 255 | /// The closest value of ``Float80`` to the payload of this variant if it matches 256 | /// ``number(_:) [case]``, `nil` otherwise. 257 | @inlinable public 258 | func `as`(_:Float80.Type) -> Float80? 259 | { 260 | self.as(JSON.Number.self)?.as(Float80.self) 261 | } 262 | #endif 263 | /// Attempts to load an instance of ``Double`` from this variant. 264 | /// 265 | /// - Returns: 266 | /// The closest value of ``Double`` to the payload of this variant if it matches 267 | /// ``number(_:) [case]``, `nil` otherwise. 268 | @inlinable public 269 | func `as`(_:Double.Type) -> Double? 270 | { 271 | self.as(JSON.Number.self)?.as(Double.self) 272 | } 273 | /// Attempts to load an instance of ``Float`` from this variant. 274 | /// 275 | /// - Returns: 276 | /// The closest value of ``Float`` to the payload of this variant if it matches 277 | /// ``number(_:) [case]``, `nil` otherwise. 278 | @inlinable public 279 | func `as`(_:Float.Type) -> Float? 280 | { 281 | self.as(JSON.Number.self)?.as(Float.self) 282 | } 283 | /// Attempts to load an instance of ``Number`` from this variant. 284 | /// 285 | /// - Returns: 286 | /// The payload of this variant, if it matches ``number(_:) [case]``, 287 | /// `nil` otherwise. 288 | /// 289 | /// > Complexity: O(1). 290 | @inlinable public 291 | func `as`(_:JSON.Number.Type) -> JSON.Number? 292 | { 293 | switch self 294 | { 295 | case .number(let number): number 296 | default: nil 297 | } 298 | } 299 | 300 | /// Attempts to load an instance of ``String`` from this variant. 301 | /// 302 | /// - Returns: 303 | /// The payload of this variant, if it matches ``string(_:) [case]``, 304 | /// `nil` otherwise. 305 | /// 306 | /// > Complexity: O(1). 307 | @inlinable public 308 | func `as`(_:String.Type) -> String? 309 | { 310 | switch self 311 | { 312 | case .string(let string): string.value 313 | default: nil 314 | } 315 | } 316 | } 317 | extension JSON.Node 318 | { 319 | /// Attempts to load an explicit ``null`` from this variant. 320 | /// 321 | /// - Returns: 322 | /// `nil` in the inner optional this variant is ``null``, 323 | // `nil` in the outer optional otherwise. 324 | @inlinable public 325 | func `as`(_:Never?.Type) -> Never?? 326 | { 327 | switch self 328 | { 329 | case .null: (nil as Never?) as Never?? 330 | default: nil as Never?? 331 | } 332 | } 333 | } 334 | extension JSON.Node 335 | { 336 | /// Attempts to unwrap an array from this variant. 337 | /// 338 | /// - Returns: 339 | /// The payload of this variant if it matches ``array(_:) [case]``, 340 | /// `nil` otherwise. 341 | /// 342 | /// > Complexity: O(1). This method does *not* perform any elementwise work. 343 | @inlinable public 344 | var array:JSON.Array? 345 | { 346 | switch self 347 | { 348 | case .array(let array): array 349 | default: nil 350 | } 351 | } 352 | /// Attempts to unwrap an object from this variant. 353 | /// 354 | /// - Returns: The payload of this variant if it matches ``object(_:) [case]``, 355 | /// the fields of the payload of this variant if it matches 356 | /// ``number(_:) [case]``, or `nil` otherwise. 357 | /// 358 | /// The order of the items reflects the order in which they appear in the 359 | /// source object. For more details about the payload, see the documentation 360 | /// for ``object(_:)``. 361 | /// 362 | /// To facilitate interoperability with decimal types, this method will also 363 | /// return a pseudo-object containing the values of ``Number.units`` and 364 | /// ``Number.places``, if this variant is a ``number(_:) [case]``. This function 365 | /// creates the pseudo-object by calling ``Object.init(encoding:)``. 366 | /// 367 | /// > Complexity: 368 | /// O(1). This method does *not* perform any elementwise work. 369 | @inlinable public 370 | var object:JSON.Object? 371 | { 372 | switch self 373 | { 374 | case .object(let items): 375 | items 376 | case .number(let number): 377 | .init(encoding: number) 378 | default: 379 | nil 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Number.Base10.Inverse.swift: -------------------------------------------------------------------------------- 1 | extension JSON.Number.Base10 2 | { 3 | /// Negative powers of 10, down to [`1e-19`](). 4 | enum Inverse 5 | { 6 | /// Returns the inverse of the given power of 10. 7 | /// - Parameters: 8 | /// - x: A positive exponent. If `x` is [`2`](), this subscript 9 | /// will return [`1e-2`](). 10 | /// - _: A ``BinaryFloatingPoint`` type. 11 | static 12 | subscript(x:Int, as _:T.Type) -> T 13 | where T:BinaryFloatingPoint 14 | { 15 | let inverses:[T] = 16 | [ 17 | 1, 18 | 1e-1, 19 | 1e-2, 20 | 21 | 1e-3, 22 | 1e-4, 23 | 1e-5, 24 | 25 | 1e-6, 26 | 1e-7, 27 | 1e-8, 28 | 29 | 1e-9, 30 | 1e-10, 31 | 1e-11, 32 | 33 | 1e-12, 34 | 1e-13, 35 | 1e-14, 36 | 37 | 1e-15, 38 | 1e-16, 39 | 1e-17, 40 | 41 | 1e-18, 42 | 1e-19, 43 | ] 44 | return inverses[x] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Number.Base10.swift: -------------------------------------------------------------------------------- 1 | extension JSON.Number 2 | { 3 | /// A namespace for decimal-related functionality. 4 | /// 5 | /// This API is used by library functions that are emitted into the client. 6 | /// Most library users should not have to call it directly. 7 | public 8 | enum Base10 9 | { 10 | /// Positive powers of 10, up to `10_000_000_000_000_000_000`. 11 | public static 12 | let Exp:[UInt64] = 13 | [ 14 | 1, 15 | 10, 16 | 100, 17 | 18 | 1_000, 19 | 10_000, 20 | 100_000, 21 | 22 | 1_000_000, 23 | 10_000_000, 24 | 100_000_000, 25 | 26 | 1_000_000_000, 27 | 10_000_000_000, 28 | 100_000_000_000, 29 | 30 | 1_000_000_000_000, 31 | 10_000_000_000_000, 32 | 100_000_000_000_000, 33 | 34 | 1_000_000_000_000_000, 35 | 10_000_000_000_000_000, 36 | 100_000_000_000_000_000, 37 | 38 | 1_000_000_000_000_000_000, 39 | 10_000_000_000_000_000_000, 40 | // UInt64.max: 41 | // 18_446_744_073_709_551_615 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Number.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A lossless representation of a numeric literal. 4 | /// 5 | /// This type is memory-efficient, and can store fixed-point numbers with 6 | /// up to 64 bits of precision. It uses all 64 bits to encode its magnitude, 7 | /// which enables it to round-trip integers of width up to ``UInt64``. 8 | @frozen public 9 | struct Number:Hashable, Equatable, Sendable 10 | { 11 | // this layout should allow instances of `Number` to fit in 2 words. 12 | // this is backed by an `Int`, but the swift compiler can optimize it 13 | // into a `UInt8`-sized field 14 | 15 | /// The sign of this numeric literal. 16 | public 17 | var sign:FloatingPointSign 18 | // cannot have an inlinable property wrapper 19 | @usableFromInline internal 20 | var _places:UInt32 21 | /// The number of decimal places this numeric literal has. 22 | /// 23 | /// > Note: 24 | /// > This property has type ``UInt64`` to facilitate computations with 25 | /// ``units``. It is backed by a ``UInt32`` and can therefore only store 26 | /// 32 bits of precision. 27 | @inlinable public 28 | var places:UInt64 29 | { 30 | .init(self._places) 31 | } 32 | /// The magnitude of this numeric literal, expressed in units of ``places``. 33 | /// 34 | /// If ``units`` is [`123`](), and ``places`` is [`2`](), that would represent 35 | /// a magnitude of [`1.23`](). 36 | public 37 | var units:UInt64 38 | /// Creates a numeric literal. 39 | /// - Parameters: 40 | /// - sign: The sign, positive or negative. 41 | /// - units: The magnitude, in units of `places`. 42 | /// - places: The number of decimal places. 43 | @inlinable public 44 | init(sign:FloatingPointSign, units:UInt64, places:UInt32 = 0) 45 | { 46 | self.sign = sign 47 | self.units = units 48 | self._places = places 49 | } 50 | } 51 | } 52 | extension JSON.Number 53 | { 54 | @inlinable public 55 | init(_ signed:T) where T:SignedInteger 56 | { 57 | self.init(sign: signed < 0 ? .minus : .plus, units: UInt64.init(signed.magnitude)) 58 | } 59 | @inlinable public 60 | init(_ unsigned:T) where T:UnsignedInteger 61 | { 62 | self.init(sign: .plus, units: UInt64.init(unsigned)) 63 | } 64 | } 65 | extension JSON.Number 66 | { 67 | /// Converts this numeric literal to an unsigned integer, if it can be 68 | /// represented exactly. 69 | /// - Parameters: 70 | /// - _: A type conforming to ``UnsignedInteger`` (and ``FixedWidthInteger``). 71 | /// - Returns: 72 | /// The value of this numeric literal as an instance of [`T`](), or 73 | /// nil if it is negative, fractional, or would overflow [`T`](). 74 | /// > Note: 75 | /// This type conversion will fail if ``places`` is non-zero, even if 76 | /// the fractional part is zero. For example, you can convert 77 | /// [`5`]() to an integer, but not [`5.0`](). This matches the behavior 78 | /// of ``ExpressibleByIntegerLiteral``. 79 | @inlinable public 80 | func `as`(_:T.Type) -> T? where T:FixedWidthInteger & UnsignedInteger 81 | { 82 | guard self.places == 0 83 | else 84 | { 85 | return nil 86 | } 87 | switch self.sign 88 | { 89 | case .minus: 90 | return self.units == 0 ? 0 : nil 91 | case .plus: 92 | return T.init(exactly: self.units) 93 | } 94 | } 95 | /// Converts this numeric literal to a signed integer, if it can be 96 | /// represented exactly. 97 | /// - Parameters: 98 | /// - _: A type conforming to ``SignedInteger`` (and ``FixedWidthInteger``). 99 | /// - Returns: 100 | /// The value of this numeric literal as an instance of [`T`](), or 101 | /// nil if it is fractional or would overflow [`T`](). 102 | /// > Note: 103 | /// This type conversion will fail if ``places`` is non-zero, even if 104 | /// the fractional part is zero. For example, you can convert 105 | /// [`5`]() to an integer, but not [`5.0`](). This matches the behavior 106 | /// of ``ExpressibleByIntegerLiteral``. 107 | @inlinable public 108 | func `as`(_:T.Type) -> T? where T:FixedWidthInteger & SignedInteger 109 | { 110 | guard self.places == 0 111 | else 112 | { 113 | return nil 114 | } 115 | switch self.sign 116 | { 117 | case .minus: 118 | let negated:Int64 = .init(bitPattern: 0 &- self.units) 119 | return negated <= 0 ? T.init(exactly: negated) : nil 120 | case .plus: 121 | return T.init(exactly: self.units) 122 | } 123 | } 124 | /// Converts this numeric literal to a fixed-point decimal, if it can be 125 | /// represented exactly. 126 | /// - Parameters: 127 | /// - _: A tuple type with fields conforming to ``SignedInteger`` 128 | /// (and ``FixedWidthInteger``). 129 | /// - Returns: 130 | /// The value of this numeric literal as an instance of 131 | /// [`(units:T, places:T)`](), or nil if the value of either 132 | /// field would overflow [`T`](). 133 | /// > Note: 134 | /// It’s possible for the `places` field to overflow before `units` does. 135 | /// For example, this will happen for the literal [`"0.0e-9999999999999999999"`](). 136 | @inlinable public 137 | func `as`(_:(units:T, places:T).Type) -> (units:T, places:T)? 138 | where T:FixedWidthInteger & SignedInteger 139 | { 140 | guard let places:T = T.init(exactly: self.places) 141 | else 142 | { 143 | return nil 144 | } 145 | switch self.sign 146 | { 147 | case .minus: 148 | let negated:Int64 = Int64.init(bitPattern: 0 &- self.units) 149 | guard negated <= 0, 150 | let units:T = T.init(exactly: negated) 151 | else 152 | { 153 | return nil 154 | } 155 | return (units: units, places: places) 156 | case .plus: 157 | guard let units:T = T.init(exactly: self.units) 158 | else 159 | { 160 | return nil 161 | } 162 | return (units: units, places: places) 163 | } 164 | } 165 | 166 | // Note: There is currently a compiler crash 167 | // 168 | // https://github.com/apple/swift/issues/63775 169 | // 170 | // that prevents ``nearest(_:)`` from being inlined into clients, 171 | // because it uses a lookup table for negative powers of ten. 172 | // Therefore, we provide manual specializations for ``Float80``, 173 | // ``Double``, and ``Float`` instead. On the bright side, this 174 | // means we don’t need to emit a giant conversion function into 175 | // the client. (We just have three giant conversion function 176 | // specializations in the library.) 177 | #if (os(Linux) || os(macOS)) && arch(x86_64) 178 | /// Converts this numeric literal to a ``Float80`` value, or its closest 179 | /// floating-point representation. 180 | public 181 | func `as`(_:Float80.Type) -> Float80 182 | { 183 | self.nearest(Float80.self) 184 | } 185 | #endif 186 | /// Converts this numeric literal to a ``Double`` value, or its closest 187 | /// floating-point representation. 188 | public 189 | func `as`(_:Double.Type) -> Double 190 | { 191 | self.nearest(Double.self) 192 | } 193 | /// Converts this numeric literal to a ``Float`` value, or its closest 194 | /// floating-point representation. 195 | public 196 | func `as`(_:Float.Type) -> Float 197 | { 198 | self.nearest(Float.self) 199 | } 200 | 201 | /// Converts this numeric literal to a floating-point value, or its closest 202 | /// floating-point representation. 203 | /// 204 | /// - Parameters: 205 | /// - _: A type conforming to ``BinaryFloatingPoint``. 206 | /// - Returns: 207 | /// The value of this numeric literal as an instance of 208 | /// [`T`](), or the value of [`T`]() closest to it. 209 | private 210 | func nearest(_:T.Type) -> T where T:BinaryFloatingPoint 211 | { 212 | var places:Int = .init(self.places), 213 | units:UInt64 = self.units 214 | // steve canon, feel free to submit a PR 215 | while places > 0 216 | { 217 | guard case (let quotient, remainder: 0) = units.quotientAndRemainder(dividingBy: 10) 218 | else 219 | { 220 | switch self.sign 221 | { 222 | case .minus: return -T.init(units) * Base10.Inverse[places, as: T.self] 223 | case .plus: return T.init(units) * Base10.Inverse[places, as: T.self] 224 | } 225 | } 226 | units = quotient 227 | places -= 1 228 | } 229 | switch self.sign 230 | { 231 | case .minus: return -T.init(units) 232 | case .plus: return T.init(units) 233 | } 234 | } 235 | } 236 | extension JSON.Number:CustomStringConvertible 237 | { 238 | /// Returns a zero-padded string representation of this numeric literal. 239 | /// 240 | /// This property always formats the number with full precision. 241 | /// If ``units`` is [`100`]() and ``places`` is [`2`](), this will return 242 | /// [`"1.00"`](). 243 | /// 244 | /// This string is guaranteed to be round-trippable; reparsing it 245 | /// will always return the same value. 246 | /// 247 | /// > Warning: 248 | /// > This string is not necessarily identical to how this literal was 249 | /// written in its original source file. In particular, if it was 250 | /// written with an exponent, the exponent would have been normalized 251 | /// into ``units`` and ``places``. 252 | public 253 | var description:String 254 | { 255 | guard self.places > 0 256 | else 257 | { 258 | switch self.sign 259 | { 260 | case .plus: return "\(self.units)" 261 | case .minus: return "-\(self.units)" 262 | } 263 | } 264 | let places:Int = .init(self.places) 265 | let unpadded:String = .init(self.units) 266 | let string:String = 267 | """ 268 | \(String.init(repeating: "0", count: Swift.max(0, 1 + places - unpadded.count)))\ 269 | \(unpadded) 270 | """ 271 | switch self.sign 272 | { 273 | case .plus: return "\(string.dropLast(places)).\(string.suffix(places))" 274 | case .minus: return "-\(string.dropLast(places)).\(string.suffix(places))" 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.Object.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A string-keyed JSON object, which can recursively contain instances of 4 | /// ``JSON``. This type is a transparent wrapper around a native 5 | /// [`[(key:String, value:JSON)]`]() array. 6 | /// 7 | /// JSON objects are more closely-related to ``KeyValuePairs`` than to 8 | /// ``Dictionary``, since object keys can occur more than once in the same 9 | /// object. However, most JSON schema allow clients to safely treat objects 10 | /// as ``Dictionary``-like containers. 11 | /// 12 | /// The order of the ``fields`` in the payload reflects the order in which they 13 | /// appear in the source object. 14 | /// 15 | /// > Warning: 16 | /// Many JSON encoders do not emit object fields in a stable order. Only 17 | /// assume a particular ordering based on careful observation or official 18 | /// documentation. 19 | /// 20 | /// The object keys are not escaped. 21 | /// 22 | /// > Warning: 23 | /// Object keys can contain arbitrary unicode text. Don’t assume the 24 | /// keys are ASCII. 25 | @frozen public 26 | struct Object 27 | { 28 | public 29 | var fields:[(key:Key, value:JSON.Node)] 30 | 31 | @inlinable public 32 | init(_ fields:[(key:Key, value:JSON.Node)]) 33 | { 34 | self.fields = fields 35 | } 36 | @inlinable public 37 | init() 38 | { 39 | self.init([]) 40 | } 41 | } 42 | } 43 | extension JSON.Object 44 | { 45 | /// Creates a pseudo-object containing integral ``Number`` values taken 46 | /// from the supplied `number`, keyed by `"units"` and `"places"` and 47 | /// wrapped in containers of type `Self`. 48 | /// 49 | /// This pseudo-object is intended for consumption by compiler-generated 50 | /// ``Codable`` implementations. Decoding it incurs a small but non-zero 51 | /// overhead when compared with calling ``Number``’s numeric casting 52 | /// methods directly. 53 | public 54 | init(encoding number:JSON.Number) 55 | { 56 | let units:JSON.Number = .init(sign: number.sign, units: number.units, places: 0), 57 | places:JSON.Number = .init(sign: .plus, units: number.places, places: 0) 58 | self.init([("units", .number(units)), ("places", .number(places))]) 59 | } 60 | } 61 | extension JSON.Object:CustomStringConvertible 62 | { 63 | /// Returns this object serialized as a minified string. 64 | /// 65 | /// Reparsing and reserializing this string is guaranteed to return the 66 | /// same string. 67 | public 68 | var description:String 69 | { 70 | """ 71 | {\(self.fields.map 72 | { 73 | "\(String.init(JSON.Literal.init($0.key.rawValue))):\($0.value)" 74 | }.joined(separator: ","))} 75 | """ 76 | } 77 | } 78 | extension JSON.Object:ExpressibleByDictionaryLiteral 79 | { 80 | @inlinable public 81 | init(dictionaryLiteral:(JSON.Key, JSON.Node)...) 82 | { 83 | self.init(dictionaryLiteral) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.TypecastError.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A decoder failed to cast a variant to an expected value type. 4 | @frozen public 5 | enum TypecastError:Equatable, Error 6 | { 7 | case null 8 | case bool 9 | case number 10 | case string 11 | case array 12 | case object 13 | } 14 | } 15 | extension JSON.TypecastError 16 | { 17 | @inlinable public 18 | init(invalid json:__shared JSON.Node) 19 | { 20 | switch json 21 | { 22 | case .null: self = .null 23 | case .bool: self = .bool 24 | case .number: self = .number 25 | case .string: self = .string 26 | case .array: self = .array 27 | case .object: self = .object 28 | } 29 | } 30 | } 31 | extension JSON.TypecastError:CustomStringConvertible 32 | { 33 | private 34 | var type:String 35 | { 36 | switch self 37 | { 38 | case .null: "null" 39 | case .bool: "bool" 40 | case .number: "number" 41 | case .string: "string" 42 | case .array: "array" 43 | case .object: "object" 44 | } 45 | } 46 | public 47 | var description:String 48 | { 49 | "cannot cast variant of type '\(self.type)' to type '\(Value.self)'" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/JSONAST/JSON.swift: -------------------------------------------------------------------------------- 1 | /// A container for some UTF-8 encoded JSON source text. 2 | @frozen public 3 | struct JSON:Sendable 4 | { 5 | public 6 | var utf8:ArraySlice 7 | 8 | @inlinable public 9 | init(utf8:ArraySlice) 10 | { 11 | self.utf8 = utf8 12 | } 13 | } 14 | extension JSON:CustomStringConvertible 15 | { 16 | public 17 | var description:String 18 | { 19 | .init(decoding: self.utf8, as: UTF8.self) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Array (ext).swift: -------------------------------------------------------------------------------- 1 | extension Array:JSONDecodable where Element:JSONDecodable 2 | { 3 | @inlinable public 4 | init(json:JSON.Node) throws 5 | { 6 | try self.init(json: try .init(json: json)) 7 | } 8 | } 9 | extension Array where Element:JSONDecodable 10 | { 11 | @inlinable public 12 | init(json:JSON.Array) throws 13 | { 14 | self = try json.map { try $0.decode(to: Element.self) } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Bool (ext).swift: -------------------------------------------------------------------------------- 1 | extension Bool:JSONDecodable 2 | { 3 | @inlinable public 4 | init(json:JSON.Node) throws 5 | { 6 | self = try json.cast { $0.as(Self.self) } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Character (ext).swift: -------------------------------------------------------------------------------- 1 | extension Character:JSONStringDecodable 2 | { 3 | /// Witnesses `Character`’s ``JSONStringDecodable`` conformance, 4 | /// throwing a ``JSON.ValueError`` instead of trapping on multi-character 5 | /// input. 6 | /// 7 | /// This is needed because its ``LosslessStringConvertible.init(_:)`` 8 | /// witness traps on invalid input instead of returning nil, which 9 | /// causes its default implementation (where [`Self:LosslessStringConvertible`]()) 10 | /// to do the same. 11 | @inlinable public 12 | init(json:JSON.Node) throws 13 | { 14 | let string:String = try .init(json: json) 15 | if string.startIndex < string.endIndex, 16 | string.index(after: string.startIndex) == string.endIndex 17 | { 18 | self = string[string.startIndex] 19 | } 20 | else 21 | { 22 | throw JSON.ValueError.init(invalid: string) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Dictionary (ext).swift: -------------------------------------------------------------------------------- 1 | extension Dictionary:JSONDecodable where Key == String, Value:JSONDecodable 2 | { 3 | /// Decodes an unordered dictionary from the given document. Dictionaries 4 | /// are not ``JSONEncodable``, because round-tripping them loses the field 5 | /// ordering information. 6 | @inlinable public 7 | init(json:JSON.Node) throws 8 | { 9 | try self.init(json: try .init(json: json)) 10 | } 11 | @inlinable public 12 | init(json:JSON.Object) throws 13 | { 14 | self.init(minimumCapacity: json.count) 15 | for field:JSON.FieldDecoder in json 16 | { 17 | if case _? = self.updateValue(try field.decode(to: Value.self), forKey: field.key) 18 | { 19 | throw JSON.ObjectKeyError.duplicate(field.key) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Double (ext).swift: -------------------------------------------------------------------------------- 1 | extension Double:JSONDecodable 2 | { 3 | @inlinable public 4 | init(json:JSON.Node) throws 5 | { 6 | self = try json.cast { $0.as(Self.self) } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Float (ext).swift: -------------------------------------------------------------------------------- 1 | extension Float:JSONDecodable 2 | { 3 | @inlinable public 4 | init(json:JSON.Node) throws 5 | { 6 | self = try json.cast { $0.as(Self.self) } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Float80 (ext).swift: -------------------------------------------------------------------------------- 1 | #if (os(Linux) || os(macOS)) && arch(x86_64) 2 | extension Float80:JSONDecodable 3 | { 4 | @inlinable public 5 | init(json:JSON.Node) throws 6 | { 7 | self = try json.cast { $0.as(Self.self) } 8 | } 9 | } 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Int (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Int16 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int16:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Int32 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int32:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Int64 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int64:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Int8 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int8:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Set (ext).swift: -------------------------------------------------------------------------------- 1 | extension Set:JSONDecodable where Element:JSONDecodable 2 | { 3 | @inlinable public 4 | init(json:JSON.Node) throws 5 | { 6 | let array:JSON.Array = try .init(json: json) 7 | 8 | self.init() 9 | self.reserveCapacity(array.count) 10 | for field:JSON.FieldDecoder in array 11 | { 12 | self.update(with: try field.decode(to: Element.self)) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/String (ext).swift: -------------------------------------------------------------------------------- 1 | extension String:JSONStringDecodable 2 | { 3 | @inlinable public 4 | init(json:JSON.Node) throws 5 | { 6 | self = try json.cast { $0.as(String.self) } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/UInt (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/UInt16 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt16:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/UInt32 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt32:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/UInt64 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt64:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/UInt8 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt8:JSONDecodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Conformances/Unicode.Scalar (ext).swift: -------------------------------------------------------------------------------- 1 | // note: the witness comes from `Unicode.Scalar`’s 2 | // ``LosslessStringConvertible`` conformance. 3 | extension Unicode.Scalar:JSONStringDecodable 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.ArrayShape.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// An efficient interface for checking the length of a decoded 4 | /// array at run time. 5 | @frozen public 6 | struct ArrayShape:Hashable, Sendable 7 | { 8 | public 9 | let count:Int 10 | 11 | @inlinable public 12 | init(count:Int) 13 | { 14 | self.count = count 15 | } 16 | } 17 | } 18 | extension JSON.ArrayShape 19 | { 20 | /// Throws an ``ArrayShapeError`` if the relevant array does not 21 | /// contain the specified number of elements. 22 | @inlinable public 23 | func expect(count:Int) throws 24 | { 25 | guard self.count == count 26 | else 27 | { 28 | throw JSON.ArrayShapeError.init(invalid: self.count, expected: .count(count)) 29 | } 30 | } 31 | /// Throws an ``ArrayShapeError`` if the number of elements in the 32 | /// relevant array is not a multiple of the specified stride. 33 | @inlinable public 34 | func expect(multipleOf stride:Int) throws 35 | { 36 | guard self.count.isMultiple(of: stride) 37 | else 38 | { 39 | throw JSON.ArrayShapeError.init(invalid: self.count, 40 | expected: .multiple(of: stride)) 41 | } 42 | } 43 | /// Converts a boolean status code into a thrown ``ArrayShapeError``. 44 | /// To generate an error, return false from the closure. 45 | @inlinable public 46 | func expect(that predicate:(_ count:Int) throws -> Bool) throws 47 | { 48 | guard try predicate(self.count) 49 | else 50 | { 51 | throw JSON.ArrayShapeError.init(invalid: self.count) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.ArrayShapeCriteria.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// What shape you expected an array to have. 4 | @frozen public 5 | enum ArrayShapeCriteria:Hashable, Sendable 6 | { 7 | case count(Int) 8 | case multiple(of:Int) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.ArrayShapeError.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// An array had an invalid scheme. 4 | @frozen public 5 | struct ArrayShapeError:Equatable, Error 6 | { 7 | public 8 | let count:Int 9 | public 10 | let expected:ArrayShapeCriteria? 11 | 12 | @inlinable public 13 | init(invalid:Int, expected:ArrayShapeCriteria? = nil) 14 | { 15 | self.count = invalid 16 | self.expected = expected 17 | } 18 | } 19 | } 20 | extension JSON.ArrayShapeError:CustomStringConvertible 21 | { 22 | public 23 | var description:String 24 | { 25 | switch self.expected 26 | { 27 | case nil: 28 | "Invalid element count (\(self.count))." 29 | 30 | case .count(let count)?: 31 | "Invalid element count (\(self.count)), expected \(count) elements." 32 | 33 | case .multiple(of: let stride)?: 34 | "Invalid element count (\(self.count)), expected multiple of \(stride)." 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.DecodingError.swift: -------------------------------------------------------------------------------- 1 | import TraceableErrors 2 | 3 | extension JSON 4 | { 5 | /// An error occurred while decoding a document field. 6 | @frozen public 7 | struct DecodingError:Error where Location:Sendable 8 | { 9 | /// The location (key or index) where the error occurred. 10 | public 11 | let location:Location 12 | /// The underlying error that occurred. 13 | public 14 | let underlying:any Error 15 | 16 | @inlinable public 17 | init(_ underlying:any Error, in location:Location) 18 | { 19 | self.location = location 20 | self.underlying = underlying 21 | } 22 | } 23 | } 24 | extension JSON.DecodingError:Equatable where Location:Equatable 25 | { 26 | /// Compares the ``location`` properties and the ``underlying`` 27 | /// errors of the operands for equality, returning [`true`]() 28 | /// if they are equal. Always returns [`false`]() if (any of) 29 | /// the underlying ``Error`` existentials are not ``Equatable``. 30 | public static 31 | func == (lhs:Self, rhs:Self) -> Bool 32 | { 33 | lhs.location == rhs.location && 34 | lhs.underlying == rhs.underlying 35 | } 36 | } 37 | extension JSON.DecodingError:TraceableError 38 | { 39 | /// Returns a single note that says 40 | /// [`"while decoding value for field '_'"`](). 41 | public 42 | var notes:[String] 43 | { 44 | ["while decoding value for field '\(self.location)'"] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.ObjectDecoder.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A thin wrapper around a native Swift dictionary providing an efficient decoding 4 | /// interface for a JSON object. 5 | @frozen public 6 | struct ObjectDecoder 7 | where CodingKey:RawRepresentable & Hashable & Sendable 8 | { 9 | public 10 | var index:[CodingKey: JSON.Node] 11 | 12 | @inlinable public 13 | init(_ index:[CodingKey: JSON.Node] = [:]) 14 | { 15 | self.index = index 16 | } 17 | } 18 | } 19 | extension JSON.ObjectDecoder:JSONDecodable 20 | { 21 | @inlinable public 22 | init(json:JSON.Node) throws 23 | { 24 | try self.init(indexing: try .init(json: json)) 25 | } 26 | } 27 | extension JSON.ObjectDecoder where CodingKey:RawRepresentable 28 | { 29 | @inlinable public 30 | init(indexing object:JSON.Object) throws 31 | { 32 | self.init(.init(minimumCapacity: object.count)) 33 | for field:JSON.FieldDecoder in object 34 | { 35 | guard let key:CodingKey = .init(rawValue: field.key) 36 | else 37 | { 38 | continue 39 | } 40 | if case _? = self.index.updateValue(field.value, forKey: key) 41 | { 42 | throw JSON.ObjectKeyError.duplicate(key) 43 | } 44 | } 45 | } 46 | } 47 | extension JSON.ObjectDecoder 48 | { 49 | @inlinable public __consuming 50 | func single() throws -> JSON.FieldDecoder 51 | { 52 | guard let (key, value):(CodingKey, JSON.Node) = self.index.first 53 | else 54 | { 55 | throw JSON.SingleKeyError.none 56 | } 57 | if self.index.count == 1 58 | { 59 | return .init(key: key, value: value) 60 | } 61 | else 62 | { 63 | throw JSON.SingleKeyError.multiple 64 | } 65 | } 66 | 67 | @inlinable public 68 | subscript(key:CodingKey) -> JSON.OptionalDecoder 69 | { 70 | .init(key: key, value: self.index[key]) 71 | } 72 | @inlinable public 73 | subscript(key:CodingKey) -> JSON.FieldDecoder? 74 | { 75 | self.index[key].map { .init(key: key, value: $0) } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.ObjectKeyError.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// An object had an invalid key scheme. 4 | @frozen public 5 | enum ObjectKeyError:Error where CodingKey:Sendable 6 | { 7 | /// An object contained more than one field with the same key. 8 | case duplicate(CodingKey) 9 | /// An object did not contain a field with the expected key. 10 | case undefined(CodingKey) 11 | } 12 | } 13 | extension JSON.ObjectKeyError:Equatable where CodingKey:Equatable 14 | { 15 | } 16 | extension JSON.ObjectKeyError:CustomStringConvertible 17 | { 18 | public 19 | var description:String 20 | { 21 | switch self 22 | { 23 | case .duplicate(let key): 24 | "duplicate key '\(key)'" 25 | case .undefined(let key): 26 | "undefined key '\(key)'" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.SingleKeyError.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | @frozen public 4 | enum SingleKeyError:Equatable, Error 5 | { 6 | case none 7 | case multiple 8 | } 9 | } 10 | extension JSON.SingleKeyError:CustomStringConvertible 11 | { 12 | public 13 | var description:String 14 | { 15 | switch self 16 | { 17 | case .none: 18 | "no keys in single-field object" 19 | case .multiple: 20 | "multiple keys in single-field object" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Decoding/JSON.ValueError.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A decoder successfully to cast a variant to an expected value type, 4 | /// but it was not a valid case of the expected destination type. 5 | @frozen public 6 | struct ValueError:Error where Value:Sendable 7 | { 8 | public 9 | let value:Value 10 | 11 | @inlinable public 12 | init(invalid value:Value) 13 | { 14 | self.value = value 15 | } 16 | } 17 | } 18 | extension JSON.ValueError:Equatable where Value:Equatable 19 | { 20 | } 21 | extension JSON.ValueError:CustomStringConvertible 22 | { 23 | public 24 | var description:String 25 | { 26 | "value '\(self.value)' does not encode a valid instance of type '\(Cases.self)'" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Fields/JSON.FieldDecoder.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | @frozen public 4 | struct FieldDecoder where Key:Sendable 5 | { 6 | public 7 | let key:Key 8 | public 9 | let value:JSON.Node 10 | 11 | @inlinable public 12 | init(key:Key, value:JSON.Node) 13 | { 14 | self.key = key 15 | self.value = value 16 | } 17 | } 18 | } 19 | extension JSON.FieldDecoder:JSON.TraceableDecoder 20 | { 21 | /// Decodes the value of this field with the given decoder. 22 | /// Throws a ``JSON.DecodingError`` wrapping the underlying 23 | /// error if decoding fails. 24 | @inlinable public 25 | func decode(with decode:(JSON.Node) throws -> T) throws -> T 26 | { 27 | do 28 | { 29 | return try decode(self.value) 30 | } 31 | catch let error 32 | { 33 | throw JSON.DecodingError.init(error, in: self.key) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Fields/JSON.OptionalDecoder.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A field that may or may not exist in a document. This type is 4 | /// the return value of ``ObjectDecoder``’s non-optional subscript, and 5 | /// is useful for obtaining structured diagnostics for “key-not-found” 6 | /// scenarios. 7 | @frozen public 8 | struct OptionalDecoder where Key:Sendable 9 | { 10 | public 11 | let key:Key 12 | public 13 | let value:JSON.Node? 14 | 15 | @inlinable public 16 | init(key:Key, value:JSON.Node?) 17 | { 18 | self.key = key 19 | self.value = value 20 | } 21 | } 22 | } 23 | extension JSON.OptionalDecoder 24 | { 25 | @inlinable public static 26 | func ?? (lhs:Self, rhs:@autoclosure () -> Self) -> Self 27 | { 28 | if case nil = lhs.value 29 | { 30 | rhs() 31 | } 32 | else 33 | { 34 | lhs 35 | } 36 | } 37 | } 38 | extension JSON.OptionalDecoder 39 | { 40 | /// Gets the value of this key, throwing a ``JSON.ObjectKeyError`` if it is nil. This is a 41 | /// distinct condition from an explicit ``JSON.Node/null`` value, which will be returned 42 | /// without throwing an error. 43 | @inlinable public 44 | func decode() throws -> JSON.Node 45 | { 46 | if let value:JSON.Node = self.value 47 | { 48 | return value 49 | } 50 | else 51 | { 52 | throw JSON.ObjectKeyError.undefined(self.key) 53 | } 54 | } 55 | } 56 | extension JSON.OptionalDecoder:JSON.TraceableDecoder 57 | { 58 | /// Decodes the value of this implicit field with the given decoder, throwing a 59 | /// ``JSON/ObjectKeyError`` if it does not exist. Throws a 60 | /// ``JSON/DecodingError`` wrapping the underlying error if decoding fails. 61 | @inlinable public 62 | func decode(with decode:(JSON.Node) throws -> T) throws -> T 63 | { 64 | // we cannot *quite* shove this into the `do` block, because we 65 | // do not want to throw a ``DecodingError`` just because the key 66 | // was not found. 67 | let value:JSON.Node = try self.decode() 68 | do 69 | { 70 | return try decode(value) 71 | } 72 | catch let error 73 | { 74 | throw JSON.DecodingError.init(error, in: key) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Fields/JSON.TraceableDecoder.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A type that represents a scope for decoding operations. 4 | public 5 | protocol TraceableDecoder 6 | { 7 | /// Attempts to load a JSON variant value and passes it to the given 8 | /// closure, returns its result. If decoding fails, the implementation 9 | /// should annotate the error with appropriate context and re-throw it. 10 | func decode(with decode:(JSON.Node) throws -> T) throws -> T 11 | } 12 | } 13 | 14 | extension JSON.TraceableDecoder 15 | { 16 | @inlinable public 17 | func decode(using _:CodingKey.Type = CodingKey.self, 18 | with decode:(JSON.ObjectDecoder) throws -> T) throws -> T 19 | { 20 | try self.decode { try decode(try .init(json: $0)) } 21 | } 22 | @inlinable public 23 | func decode(with decode:(JSON.ObjectDecoder) throws -> T) throws -> T 24 | { 25 | try self.decode { try decode(try .init(json: $0)) } 26 | } 27 | @inlinable public 28 | func decode(with decode:(JSON.Array) throws -> T) throws -> T 29 | { 30 | try self.decode { try decode(try .init(json: $0)) } 31 | } 32 | 33 | @inlinable public 34 | func decode(as _:Decodable.Type, 35 | with decode:(Decodable) throws -> T) throws -> T where Decodable:JSONDecodable 36 | { 37 | try self.decode { try decode(try .init(json: $0)) } 38 | } 39 | @inlinable public 40 | func decode( 41 | to _:Decodable.Type = Decodable.self) throws -> Decodable where Decodable:JSONDecodable 42 | { 43 | try self.decode(with: Decodable.init(json:)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/JSON.Array (ext).swift: -------------------------------------------------------------------------------- 1 | extension JSON.Array 2 | { 3 | @inlinable public 4 | var shape:JSON.ArrayShape 5 | { 6 | .init(count: self.count) 7 | } 8 | } 9 | extension JSON.Array:RandomAccessCollection 10 | { 11 | @inlinable public 12 | var startIndex:Int 13 | { 14 | self.elements.startIndex 15 | } 16 | @inlinable public 17 | var endIndex:Int 18 | { 19 | self.elements.endIndex 20 | } 21 | @inlinable public 22 | subscript(index:Int) -> JSON.FieldDecoder 23 | { 24 | .init(key: index, value: self.elements[index]) 25 | } 26 | } 27 | extension JSON.Array:JSONDecodable 28 | { 29 | @inlinable public 30 | init(json:JSON.Node) throws 31 | { 32 | self = try json.cast(with: \.array) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/JSON.Object (ext).swift: -------------------------------------------------------------------------------- 1 | extension JSON.Object:RandomAccessCollection 2 | { 3 | @inlinable public 4 | var startIndex:Int 5 | { 6 | self.fields.startIndex 7 | } 8 | @inlinable public 9 | var endIndex:Int 10 | { 11 | self.fields.endIndex 12 | } 13 | @inlinable public 14 | subscript(index:Int) -> JSON.FieldDecoder 15 | { 16 | let field:(key:JSON.Key, value:JSON.Node) = self.fields[index] 17 | return .init(key: field.key.rawValue, value: field.value) 18 | } 19 | } 20 | extension JSON.Object:JSONDecodable 21 | { 22 | @inlinable public 23 | init(json:JSON.Node) throws 24 | { 25 | self = try json.cast(with: \.object) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/JSONDecodable.swift: -------------------------------------------------------------------------------- 1 | /// A type that can be decoded from a JSON variant value. 2 | public 3 | protocol JSONDecodable 4 | { 5 | /// Attempts to cast a JSON variant backed by some storage type to an 6 | /// instance of this type. The implementation can copy the contents 7 | /// of the backing storage if needed. 8 | init(json:JSON.Node) throws 9 | } 10 | extension JSONDecodable where Self:SignedInteger & FixedWidthInteger 11 | { 12 | @inlinable public 13 | init(json:JSON.Node) throws 14 | { 15 | self = try json.cast { try $0.as(Self.self) } 16 | } 17 | } 18 | extension JSONDecodable where Self:UnsignedInteger & FixedWidthInteger 19 | { 20 | @inlinable public 21 | init(json:JSON.Node) throws 22 | { 23 | self = try json.cast { try $0.as(Self.self) } 24 | } 25 | } 26 | extension JSONDecodable where Self:RawRepresentable, RawValue:JSONDecodable & Sendable 27 | { 28 | @inlinable public 29 | init(json:JSON.Node) throws 30 | { 31 | let rawValue:RawValue = try .init(json: json) 32 | if let value:Self = .init(rawValue: rawValue) 33 | { 34 | self = value 35 | } 36 | else 37 | { 38 | throw JSON.ValueError.init(invalid: rawValue) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/JSONObjectDecodable.swift: -------------------------------------------------------------------------------- 1 | /// A type that can be decoded from a JSON dictionary-decoder. 2 | public 3 | protocol JSONObjectDecodable:JSONDecodable 4 | { 5 | associatedtype CodingKey:RawRepresentable & Hashable & Sendable = JSON.Key 6 | 7 | init(json:JSON.ObjectDecoder) throws 8 | } 9 | extension JSONObjectDecodable 10 | { 11 | @inlinable public 12 | init(json:JSON.Object) throws 13 | { 14 | try self.init(json: try .init(indexing: json)) 15 | } 16 | @inlinable public 17 | init(json:JSON.Node) throws 18 | { 19 | try self.init(json: try .init(json: json)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/JSONStringDecodable.swift: -------------------------------------------------------------------------------- 1 | /// A type that can be decoded from a JSON UTF-8 string. This protocol 2 | /// exists to allow types that also conform to ``LosslessStringConvertible`` 3 | /// to opt-in to automatic ``JSONDecodable`` conformance as well. 4 | public 5 | protocol JSONStringDecodable:JSONDecodable 6 | { 7 | /// Converts a string to an instance of this type. This requirement 8 | /// restates its counterpart in ``LosslessStringConvertible`` if 9 | /// `Self` also conforms to it. 10 | init?(_ description:String) 11 | } 12 | extension JSONStringDecodable 13 | { 14 | /// Attempts to cast the given variant value to a string, and then 15 | /// delegates to this type’s ``init(_:)`` witness. 16 | /// 17 | /// This default implementation is provided on an extension on a 18 | /// dedicated protocol rather than an extension on ``JSONDecodable`` 19 | /// itself to prevent unexpected behavior for types (such as ``Int``) 20 | /// who implement ``LosslessStringConvertible``, but expect to be 21 | /// decoded from a variant value that is not a string. 22 | @inlinable public 23 | init(json:JSON.Node) throws 24 | { 25 | let string:String = try .init(json: json) 26 | if let value:Self = .init(string) 27 | { 28 | self = value 29 | } 30 | else 31 | { 32 | throw JSON.ValueError.init(invalid: string) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Never (ext).swift: -------------------------------------------------------------------------------- 1 | extension Never:JSONDecodable 2 | { 3 | /// Always throws a ``JSON.TypecastError``. 4 | @inlinable public 5 | init(json:JSON.Node) throws 6 | { 7 | throw JSON.TypecastError.init(invalid: json) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/Optional (ext).swift: -------------------------------------------------------------------------------- 1 | extension Optional:JSONDecodable where Wrapped:JSONDecodable 2 | { 3 | @inlinable public 4 | init(json:JSON.Node) throws 5 | { 6 | if case .null = json 7 | { 8 | self = .none 9 | } 10 | else 11 | { 12 | self = .some(try .init(json: json)) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/JSONDecoding/exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import struct JSONAST.JSON 2 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Array (ext).swift: -------------------------------------------------------------------------------- 1 | extension Array:JSONEncodable where Element:JSONEncodable 2 | { 3 | @inlinable public 4 | func encode(to json:inout JSON) 5 | { 6 | { 7 | for element:Element in self 8 | { 9 | $0[+] = element 10 | } 11 | } (&json[as: JSON.ArrayEncoder.self]) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/ArraySlice (ext).swift: -------------------------------------------------------------------------------- 1 | extension ArraySlice:JSONEncodable where Element:JSONEncodable 2 | { 3 | @inlinable public 4 | func encode(to json:inout JSON) 5 | { 6 | { 7 | for element:Element in self 8 | { 9 | $0[+] = element 10 | } 11 | } (&json[as: JSON.ArrayEncoder.self]) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Bool (ext).swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension Bool:JSONEncodable 4 | { 5 | @inlinable public 6 | func encode(to json:inout JSON) 7 | { 8 | json += JSON.Literal.init(self) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Character (ext).swift: -------------------------------------------------------------------------------- 1 | extension Character:JSONStringEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Int (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Int16 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int16:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Int32 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int32:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Int64 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int64:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Int8 (ext).swift: -------------------------------------------------------------------------------- 1 | extension Int8:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/StaticString (ext).swift: -------------------------------------------------------------------------------- 1 | extension StaticString:JSONStringEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/String (ext).swift: -------------------------------------------------------------------------------- 1 | extension String:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Substring (ext).swift: -------------------------------------------------------------------------------- 1 | extension Substring:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/UInt (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/UInt16 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt16:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/UInt32 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt32:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/UInt64 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt64:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/UInt8 (ext).swift: -------------------------------------------------------------------------------- 1 | extension UInt8:JSONEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Conformances/Unicode.Scalar (ext).swift: -------------------------------------------------------------------------------- 1 | extension Unicode.Scalar:JSONStringEncodable 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Encoders/JSON.ArrayEncoder.Index.swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension JSON.ArrayEncoder 4 | { 5 | /// A syntactical abstraction used to express the “end index” of an array. This type has no 6 | /// inhabitants. 7 | @frozen public 8 | enum Index 9 | { 10 | } 11 | } 12 | extension JSON.ArrayEncoder.Index 13 | { 14 | /// A syntactical symbol used to express the “end index” of an array. 15 | @inlinable public static prefix 16 | func + (_:Self) 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Encoders/JSON.ArrayEncoder.swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension JSON 4 | { 5 | @frozen public 6 | struct ArrayEncoder:Sendable 7 | { 8 | @usableFromInline internal 9 | var first:Bool 10 | @usableFromInline internal 11 | var json:JSON 12 | 13 | @inlinable internal 14 | init(json:JSON) 15 | { 16 | self.first = true 17 | self.json = json 18 | } 19 | } 20 | } 21 | extension JSON.ArrayEncoder:JSON.InlineEncoder 22 | { 23 | @inlinable internal static 24 | func move(_ json:inout JSON) -> Self 25 | { 26 | json.utf8.append(0x5B) // '[' 27 | defer { json.utf8 = [] } 28 | return .init(json: json) 29 | } 30 | @inlinable internal mutating 31 | func move() -> JSON 32 | { 33 | self.first = true 34 | self.json.utf8.append(0x5D) // ']' 35 | defer { self.json.utf8 = [] } 36 | return self.json 37 | } 38 | } 39 | extension JSON.ArrayEncoder 40 | { 41 | /// Creates a nested JSON encoding context. 42 | /// 43 | /// This accessor isn’t very useful on its own, rather, you should chain it with a call to 44 | /// ``JSON.callAsFunction(_:yield:)`` to bind the context to a particular coding key. 45 | /// 46 | /// You can also encode values directly with this accessor, via the `encode(to: &$0.next)` 47 | /// pattern, although the ``subscript(_:)`` setter is probably more convenient for that. 48 | @inlinable public 49 | var next:JSON 50 | { 51 | _read 52 | { 53 | yield .init(utf8: []) 54 | } 55 | _modify 56 | { 57 | if self.first 58 | { 59 | self.first = false 60 | } 61 | else 62 | { 63 | self.json.utf8.append(0x2C) // ',' 64 | } 65 | 66 | yield &self.json 67 | } 68 | } 69 | 70 | /// Creates a nested object encoding context. 71 | @inlinable public mutating 72 | func callAsFunction(_:Key.Type = Key.self, 73 | _ yield:(inout JSON.ObjectEncoder) -> ()) 74 | { 75 | yield(&self.next[as: JSON.ObjectEncoder.self]) 76 | } 77 | 78 | /// Creates a nested array encoding context. 79 | @inlinable public mutating 80 | func callAsFunction( 81 | _ yield:(inout JSON.ArrayEncoder) -> ()) 82 | { 83 | yield(&self.next[as: Self.self]) 84 | } 85 | 86 | /// Appends a value to the array. 87 | /// 88 | /// Why a subscript and not an `append` method? Because we often want to optionally append a 89 | /// value while building an array, and the subscript syntax is more convenient for that. 90 | @inlinable public 91 | subscript(_:(Index) -> Void) -> Encodable? where Encodable:JSONEncodable 92 | { 93 | get { nil } 94 | set (value) { value?.encode(to: &self.next) } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Encoders/JSON.InlineEncoder.swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension JSON 4 | { 5 | @usableFromInline 6 | protocol InlineEncoder 7 | { 8 | static 9 | func move(_ json:inout JSON) -> Self 10 | 11 | mutating 12 | func move() -> JSON 13 | } 14 | } 15 | extension JSON.InlineEncoder 16 | { 17 | @inlinable static 18 | var empty:Self 19 | { 20 | var json:JSON = .init(utf8: []) 21 | return .move(&json) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Encoders/JSON.Literal (ext).swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension JSON.Literal 4 | { 5 | /// Encodes `null` to the provided JSON stream. 6 | @inlinable internal static 7 | func += (json:inout JSON, self:Self) 8 | { 9 | json.utf8 += "null".utf8 10 | } 11 | } 12 | extension JSON.Literal 13 | { 14 | /// Encodes `true` or `false` to the provided JSON stream. 15 | @inlinable internal static 16 | func += (json:inout JSON, self:Self) 17 | { 18 | json.utf8 += (self.value ? "true" : "false").utf8 19 | } 20 | } 21 | extension JSON.Literal where Value:BinaryInteger 22 | { 23 | /// Encodes this literal’s integer ``value`` to the provided JSON stream. The value’s 24 | /// ``CustomStringConvertible description`` witness must format the value in base-10. 25 | @inlinable internal static 26 | func += (json:inout JSON, self:Self) 27 | { 28 | json.utf8 += self.value.description.utf8 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Encoders/JSON.ObjectEncoder.swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension JSON 4 | { 5 | @frozen public 6 | struct ObjectEncoder:Sendable 7 | { 8 | @usableFromInline 9 | var first:Bool 10 | @usableFromInline 11 | var json:JSON 12 | 13 | @inlinable 14 | init(json:JSON) 15 | { 16 | self.first = true 17 | self.json = json 18 | } 19 | } 20 | } 21 | extension JSON.ObjectEncoder:JSON.InlineEncoder 22 | { 23 | @inlinable static 24 | func move(_ json:inout JSON) -> Self 25 | { 26 | json.utf8.append(0x7B) // '{' 27 | defer { json.utf8 = [] } 28 | return .init(json: json) 29 | } 30 | @inlinable mutating 31 | func move() -> JSON 32 | { 33 | self.first = true 34 | self.json.utf8.append(0x7D) // '}' 35 | defer { self.json.utf8 = [] } 36 | return self.json 37 | } 38 | } 39 | extension JSON.ObjectEncoder 40 | { 41 | @inlinable 42 | subscript(with key:String) -> JSON 43 | { 44 | _read 45 | { 46 | yield .init(utf8: []) 47 | } 48 | _modify 49 | { 50 | if self.first 51 | { 52 | self.first = false 53 | } 54 | else 55 | { 56 | self.json.utf8.append(0x2C) // ',' 57 | } 58 | 59 | self.json += JSON.Literal.init(key) 60 | self.json.utf8.append(0x3A) // ':' 61 | 62 | yield &self.json 63 | } 64 | } 65 | } 66 | extension JSON.ObjectEncoder 67 | { 68 | @inlinable public 69 | subscript(key:String) -> JSON 70 | { 71 | _read { yield self[with: key] } 72 | _modify { yield &self[with: key] } 73 | } 74 | 75 | @inlinable public 76 | subscript(key:String, yield:(inout JSON.ObjectEncoder) -> ()) -> Void 77 | { 78 | mutating 79 | get { yield(&self[key][as: JSON.ObjectEncoder.self]) } 80 | } 81 | 82 | @inlinable public 83 | subscript(key:String, yield:(inout JSON.ArrayEncoder) -> ()) -> Void 84 | { 85 | mutating 86 | get { yield(&self[with: key][as: JSON.ArrayEncoder.self]) } 87 | } 88 | 89 | @inlinable public 90 | subscript(key:String) -> Encodable? where Encodable:JSONEncodable 91 | { 92 | get { nil } 93 | set (value) { value?.encode(to: &self[with: key]) } 94 | } 95 | } 96 | extension JSON.ObjectEncoder where Key:RawRepresentable 97 | { 98 | @inlinable public 99 | subscript(key:Key) -> JSON 100 | { 101 | _read { yield self[with: key.rawValue] } 102 | _modify { yield &self[with: key.rawValue] } 103 | } 104 | 105 | @inlinable public 106 | subscript(key:Key, yield:(inout JSON.ObjectEncoder) -> ()) -> Void 107 | { 108 | mutating 109 | get { yield(&self[key][as: JSON.ObjectEncoder.self]) } 110 | } 111 | 112 | @inlinable public 113 | subscript(key:Key, yield:(inout JSON.ArrayEncoder) -> ()) -> Void 114 | { 115 | mutating 116 | get { yield(&self[key][as: JSON.ArrayEncoder.self]) } 117 | } 118 | 119 | @inlinable public 120 | subscript(key:Key) -> Encodable? where Encodable:JSONEncodable 121 | { 122 | get { nil } 123 | set (value) { value?.encode(to: &self[key]) } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/JSON (ext).swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension JSON 4 | { 5 | @inlinable 6 | subscript(as _:Encoder.Type = Encoder.self) -> Encoder 7 | where Encoder:JSON.InlineEncoder 8 | { 9 | _read 10 | { 11 | yield .empty 12 | } 13 | _modify 14 | { 15 | var encoder:Encoder = .move(&self) 16 | defer { self = encoder.move() } 17 | yield &encoder 18 | } 19 | } 20 | } 21 | extension JSON 22 | { 23 | @inlinable public mutating 24 | func callAsFunction(_:CodingKey.Type, 25 | yield:(inout JSON.ObjectEncoder) -> ()) -> Void 26 | { 27 | yield(&self[as: JSON.ObjectEncoder.self]) 28 | } 29 | } 30 | extension JSON 31 | { 32 | @inlinable public static 33 | func array( 34 | with encode:(inout ArrayEncoder) async throws -> Void) async rethrows -> Self 35 | { 36 | var encoder:ArrayEncoder = .empty 37 | try await encode(&encoder) 38 | return encoder.move() 39 | } 40 | 41 | @inlinable public static 42 | func array( 43 | with encode:(inout ArrayEncoder) throws -> Void) rethrows -> Self 44 | { 45 | var encoder:ArrayEncoder = .empty 46 | try encode(&encoder) 47 | return encoder.move() 48 | } 49 | 50 | @inlinable public static 51 | func object( 52 | encoding keys:CodingKey.Type = CodingKey.self, 53 | with encode:(inout ObjectEncoder) throws -> Void) rethrows -> Self 54 | { 55 | var encoder:ObjectEncoder = .empty 56 | try encode(&encoder) 57 | return encoder.move() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/JSONEncodable.swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | public 4 | protocol JSONEncodable 5 | { 6 | func encode(to json:inout JSON) 7 | } 8 | extension JSONEncodable where Self:StringProtocol 9 | { 10 | /// Encodes the UTF-8 bytes of this instance as a JSON string. 11 | @inlinable public 12 | func encode(to json:inout JSON) 13 | { 14 | json += JSON.Literal.init(self) 15 | } 16 | } 17 | extension JSONEncodable where Self:BinaryInteger 18 | { 19 | @inlinable public 20 | func encode(to json:inout JSON) 21 | { 22 | json += JSON.Literal.init(self) 23 | } 24 | } 25 | extension JSONEncodable where Self:RawRepresentable, RawValue:JSONEncodable 26 | { 27 | @inlinable public 28 | func encode(to json:inout JSON) 29 | { 30 | self.rawValue.encode(to: &json) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/JSONObjectEncodable.swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | public 4 | protocol JSONObjectEncodable:JSONEncodable 5 | { 6 | associatedtype CodingKey:RawRepresentable = JSON.Key 7 | 8 | func encode(to json:inout JSON.ObjectEncoder) 9 | } 10 | extension JSONObjectEncodable 11 | { 12 | @inlinable public 13 | func encode(to json:inout JSON) 14 | { 15 | self.encode(to: &json[as: JSON.ObjectEncoder.self]) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/JSONStringEncodable.swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | /// A type that can be encoded to a JSON string. This protocol 4 | /// exists to allow types that also conform to ``LosslessStringConvertible`` 5 | /// to opt-in to automatic ``JSONEncodable`` conformance as well. 6 | public 7 | protocol JSONStringEncodable:JSONEncodable 8 | { 9 | /// Converts an instance of this type to a string. This requirement 10 | /// restates its counterpart in ``CustomStringConvertible`` if 11 | /// `Self` also conforms to it. 12 | var description:String { get } 13 | } 14 | extension JSONStringEncodable 15 | { 16 | /// Encodes the ``description`` of this instance as a JSON string. 17 | /// 18 | /// This default implementation is provided on an extension on a 19 | /// dedicated protocol rather than an extension on ``JSONEncodable`` 20 | /// itself to prevent unexpected behavior for types (such as ``Int``) 21 | /// who implement ``LosslessStringConvertible``, but expect to be 22 | /// encoded as something besides a string. 23 | @inlinable public 24 | func encode(to json:inout JSON) 25 | { 26 | json += JSON.Literal.init(self.description) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Never (ext).swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension Never:JSONEncodable 4 | { 5 | /// Does nothing. 6 | @inlinable public 7 | func encode(to _:inout JSON) 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/Optional (ext).swift: -------------------------------------------------------------------------------- 1 | import JSONAST 2 | 3 | extension Optional:JSONEncodable where Wrapped:JSONEncodable 4 | { 5 | /// Encodes the wrapped value if it exists, or an explicit `null` if it does not. 6 | @inlinable public 7 | func encode(to json:inout JSON) 8 | { 9 | if let self 10 | { 11 | self.encode(to: &json) 12 | } 13 | else 14 | { 15 | json += JSON.Literal.init(nil) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/JSONEncoding/exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import struct JSONAST.JSON 2 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/CodableCompatibility/JSON.KeyedDecoder.Super.swift: -------------------------------------------------------------------------------- 1 | extension JSON.KeyedDecoder 2 | { 3 | enum Super:String, CodingKey 4 | { 5 | case `super` 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/CodableCompatibility/JSON.KeyedDecoder.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | struct KeyedDecoder where Key:CodingKey 4 | { 5 | let codingPath:[any CodingKey] 6 | let allKeys:[Key] 7 | let items:[JSON.Key: JSON.Node] 8 | 9 | init(_ dictionary:JSON.ObjectDecoder, path:[any CodingKey]) 10 | { 11 | self.codingPath = path 12 | self.items = dictionary.index 13 | self.allKeys = self.items.keys.compactMap { .init(stringValue: $0.rawValue) } 14 | } 15 | } 16 | } 17 | extension JSON.KeyedDecoder 18 | { 19 | public 20 | func contains(_ key:Key) -> Bool 21 | { 22 | self.items.keys.contains(.init(key)) 23 | } 24 | // local `Key` type may be different from the dictionary’s `Key` type 25 | func diagnose(_ key:some CodingKey, 26 | _ decode:(JSON.Node) throws -> T?) throws -> T 27 | { 28 | var path:[any CodingKey] 29 | { 30 | self.codingPath + CollectionOfOne.init(key) 31 | } 32 | guard let value:JSON.Node = self.items[.init(key)] 33 | else 34 | { 35 | let context:DecodingError.Context = .init(codingPath: path, 36 | debugDescription: "key '\(key)' not found") 37 | throw DecodingError.keyNotFound(key, context) 38 | } 39 | do 40 | { 41 | if let decoded:T = try decode(value) 42 | { 43 | return decoded 44 | } 45 | 46 | throw DecodingError.init(annotating: JSON.TypecastError.init(invalid: value), 47 | initializing: T.self, 48 | path: path) 49 | } 50 | catch let error 51 | { 52 | throw DecodingError.init(annotating: error, 53 | initializing: T.self, 54 | path: path) 55 | } 56 | } 57 | } 58 | 59 | extension JSON.KeyedDecoder:KeyedDecodingContainerProtocol 60 | { 61 | public 62 | func decode(_:T.Type, forKey key:Key) throws -> T where T:Decodable 63 | { 64 | try .init(from: try self.singleValueContainer(forKey: key)) 65 | } 66 | func decodeNil(forKey key:Key) throws -> Bool 67 | { 68 | try self.diagnose(key) { $0.as(Never?.self) != nil } 69 | } 70 | public 71 | func decode(_:Bool.Type, forKey key:Key) throws -> Bool 72 | { 73 | try self.diagnose(key) { $0.as(Bool.self) } 74 | } 75 | public 76 | func decode(_:Float.Type, forKey key:Key) throws -> Float 77 | { 78 | try self.diagnose(key) { $0.as(Float.self) } 79 | } 80 | public 81 | func decode(_:Double.Type, forKey key:Key) throws -> Double 82 | { 83 | try self.diagnose(key) { $0.as(Double.self) } 84 | } 85 | public 86 | func decode(_:String.Type, forKey key:Key) throws -> String 87 | { 88 | try self.diagnose(key) { $0.as(String.self) } 89 | } 90 | public 91 | func decode(_:Int.Type, forKey key:Key) throws -> Int 92 | { 93 | try self.diagnose(key) { try $0.as(Int.self) } 94 | } 95 | public 96 | func decode(_:Int64.Type, forKey key:Key) throws -> Int64 97 | { 98 | try self.diagnose(key) { try $0.as(Int64.self) } 99 | } 100 | public 101 | func decode(_:Int32.Type, forKey key:Key) throws -> Int32 102 | { 103 | try self.diagnose(key) { try $0.as(Int32.self) } 104 | } 105 | public 106 | func decode(_:Int16.Type, forKey key:Key) throws -> Int16 107 | { 108 | try self.diagnose(key) { try $0.as(Int16.self) } 109 | } 110 | public 111 | func decode(_:Int8.Type, forKey key:Key) throws -> Int8 112 | { 113 | try self.diagnose(key) { try $0.as(Int8.self) } 114 | } 115 | public 116 | func decode(_:UInt.Type, forKey key:Key) throws -> UInt 117 | { 118 | try self.diagnose(key) { try $0.as(UInt.self) } 119 | } 120 | public 121 | func decode(_:UInt64.Type, forKey key:Key) throws -> UInt64 122 | { 123 | try self.diagnose(key) { try $0.as(UInt64.self) } 124 | } 125 | public 126 | func decode(_:UInt32.Type, forKey key:Key) throws -> UInt32 127 | { 128 | try self.diagnose(key) { try $0.as(UInt32.self) } 129 | } 130 | public 131 | func decode(_:UInt16.Type, forKey key:Key) throws -> UInt16 132 | { 133 | try self.diagnose(key) { try $0.as(UInt16.self) } 134 | } 135 | public 136 | func decode(_:UInt8.Type, forKey key:Key) throws -> UInt8 137 | { 138 | try self.diagnose(key) { try $0.as(UInt8.self) } 139 | } 140 | 141 | func superDecoder() throws -> any Decoder 142 | { 143 | try self.singleValueContainer(forKey: Super.super, typed: Super.self) 144 | } 145 | public 146 | func superDecoder(forKey key:Key) throws -> any Decoder 147 | { 148 | try self.singleValueContainer(forKey: key) as any Decoder 149 | } 150 | 151 | public 152 | func singleValueContainer(forKey key:NestedKey, 153 | typed _:NestedKey.Type = NestedKey.self) throws -> JSON.SingleValueDecoder 154 | where NestedKey:CodingKey 155 | { 156 | let value:JSON.Node = try self.diagnose(key){ $0 } 157 | let decoder:JSON.SingleValueDecoder = .init(value, 158 | path: self.codingPath + CollectionOfOne.init(key)) 159 | return decoder 160 | } 161 | public 162 | func nestedUnkeyedContainer(forKey key:Key) throws -> any UnkeyedDecodingContainer 163 | { 164 | let path:[any CodingKey] = self.codingPath + CollectionOfOne.init(key) 165 | let container:JSON.UnkeyedDecoder = 166 | .init(try self.diagnose(key, \.array), path: path) 167 | return container as any UnkeyedDecodingContainer 168 | } 169 | public 170 | func nestedContainer(keyedBy _:NestedKey.Type, 171 | forKey key:Key) throws -> KeyedDecodingContainer 172 | { 173 | let path:[any CodingKey] = self.codingPath + CollectionOfOne.init(key) 174 | let container:JSON.KeyedDecoder = 175 | .init(try .init(indexing: try self.diagnose(key, \.object)), path: path) 176 | return .init(container) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/CodableCompatibility/JSON.Node (ext).swift: -------------------------------------------------------------------------------- 1 | extension JSON.Node:Decoder 2 | { 3 | @inlinable public 4 | var codingPath:[any CodingKey] 5 | { 6 | [] 7 | } 8 | @inlinable public 9 | var userInfo:[CodingUserInfoKey: Any] 10 | { 11 | [:] 12 | } 13 | 14 | public 15 | func singleValueContainer() -> any SingleValueDecodingContainer 16 | { 17 | JSON.SingleValueDecoder.init(self, path: []) as any SingleValueDecodingContainer 18 | } 19 | public 20 | func unkeyedContainer() throws -> any UnkeyedDecodingContainer 21 | { 22 | try JSON.SingleValueDecoder.init(self, path: []).unkeyedContainer() 23 | } 24 | public 25 | func container(keyedBy _:Key.Type) throws -> KeyedDecodingContainer 26 | where Key:CodingKey 27 | { 28 | try JSON.SingleValueDecoder.init(self, path: []).container(keyedBy: Key.self) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/CodableCompatibility/JSON.SingleValueDecoder.swift: -------------------------------------------------------------------------------- 1 | import JSONDecoding 2 | 3 | extension JSON 4 | { 5 | /// A single-value decoding container, for use with compiler-generated ``Decodable`` 6 | /// implementations. 7 | public 8 | struct SingleValueDecoder 9 | { 10 | let value:JSON.Node 11 | public 12 | let codingPath:[any CodingKey] 13 | public 14 | let userInfo:[CodingUserInfoKey: Any] 15 | 16 | public 17 | init(_ value:JSON.Node, path:[any CodingKey], 18 | userInfo:[CodingUserInfoKey: Any] = [:]) 19 | { 20 | self.value = value 21 | self.codingPath = path 22 | self.userInfo = userInfo 23 | } 24 | } 25 | } 26 | extension JSON.SingleValueDecoder 27 | { 28 | func diagnose(_ decode:(JSON.Node) throws -> T?) throws -> T 29 | { 30 | do 31 | { 32 | if let decoded:T = try decode(value) 33 | { 34 | return decoded 35 | } 36 | 37 | throw DecodingError.init(annotating: JSON.TypecastError.init(invalid: value), 38 | initializing: T.self, 39 | path: self.codingPath) 40 | } 41 | catch let error 42 | { 43 | throw DecodingError.init(annotating: error, 44 | initializing: T.self, 45 | path: self.codingPath) 46 | } 47 | } 48 | } 49 | extension JSON.SingleValueDecoder:Decoder 50 | { 51 | public 52 | func singleValueContainer() -> any SingleValueDecodingContainer 53 | { 54 | self as any SingleValueDecodingContainer 55 | } 56 | public 57 | func unkeyedContainer() throws -> any UnkeyedDecodingContainer 58 | { 59 | JSON.UnkeyedDecoder.init(try self.diagnose(\.array), 60 | path: self.codingPath) as any UnkeyedDecodingContainer 61 | } 62 | public 63 | func container(keyedBy _:Key.Type) throws -> KeyedDecodingContainer 64 | where Key:CodingKey 65 | { 66 | let container:JSON.KeyedDecoder = 67 | .init(try .init(indexing: try self.diagnose(\.object)), path: self.codingPath) 68 | return .init(container) 69 | } 70 | } 71 | 72 | extension JSON.SingleValueDecoder:SingleValueDecodingContainer 73 | { 74 | public 75 | func decode(_:T.Type) throws -> T where T:Decodable 76 | { 77 | try .init(from: self) 78 | } 79 | public 80 | func decodeNil() -> Bool 81 | { 82 | self.value.as(Never?.self) != nil 83 | } 84 | public 85 | func decode(_:Bool.Type) throws -> Bool 86 | { 87 | try self.diagnose { $0.as(Bool.self) } 88 | } 89 | public 90 | func decode(_:Float.Type) throws -> Float 91 | { 92 | try self.diagnose { $0.as(Float.self) } 93 | } 94 | public 95 | func decode(_:Double.Type) throws -> Double 96 | { 97 | try self.diagnose { $0.as(Double.self) } 98 | } 99 | public 100 | func decode(_:String.Type) throws -> String 101 | { 102 | try self.diagnose { $0.as(String.self) } 103 | } 104 | public 105 | func decode(_:Int.Type) throws -> Int 106 | { 107 | try self.diagnose { try $0.as(Int.self) } 108 | } 109 | public 110 | func decode(_:Int64.Type) throws -> Int64 111 | { 112 | try self.diagnose { try $0.as(Int64.self) } 113 | } 114 | public 115 | func decode(_:Int32.Type) throws -> Int32 116 | { 117 | try self.diagnose { try $0.as(Int32.self) } 118 | } 119 | public 120 | func decode(_:Int16.Type) throws -> Int16 121 | { 122 | try self.diagnose { try $0.as(Int16.self) } 123 | } 124 | public 125 | func decode(_:Int8.Type) throws -> Int8 126 | { 127 | try self.diagnose { try $0.as(Int8.self) } 128 | } 129 | public 130 | func decode(_:UInt.Type) throws -> UInt 131 | { 132 | try self.diagnose { try $0.as(UInt.self) } 133 | } 134 | public 135 | func decode(_:UInt64.Type) throws -> UInt64 136 | { 137 | try self.diagnose { try $0.as(UInt64.self) } 138 | } 139 | public 140 | func decode(_:UInt32.Type) throws -> UInt32 141 | { 142 | try self.diagnose { try $0.as(UInt32.self) } 143 | } 144 | public 145 | func decode(_:UInt16.Type) throws -> UInt16 146 | { 147 | try self.diagnose { try $0.as(UInt16.self) } 148 | } 149 | public 150 | func decode(_:UInt8.Type) throws -> UInt8 151 | { 152 | try self.diagnose { try $0.as(UInt8.self) } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/CodableCompatibility/JSON.UnkeyedDecoder.Index.swift: -------------------------------------------------------------------------------- 1 | extension JSON.UnkeyedDecoder 2 | { 3 | struct Index:CodingKey 4 | { 5 | let value:Int 6 | var intValue:Int? 7 | { 8 | self.value 9 | } 10 | var stringValue:String 11 | { 12 | "\(self.value)" 13 | } 14 | 15 | init(intValue:Int) 16 | { 17 | self.value = intValue 18 | } 19 | init?(stringValue:String) 20 | { 21 | guard let value:Int = Int.init(stringValue) 22 | else 23 | { 24 | return nil 25 | } 26 | self.value = value 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/CodableCompatibility/JSON.UnkeyedDecoder.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | struct UnkeyedDecoder 4 | { 5 | public 6 | let codingPath:[any CodingKey] 7 | public 8 | var currentIndex:Int 9 | let elements:[JSON.Node] 10 | 11 | public 12 | init(_ array:JSON.Array, path:[any CodingKey]) 13 | { 14 | self.codingPath = path 15 | self.elements = array.elements 16 | self.currentIndex = self.elements.startIndex 17 | } 18 | } 19 | } 20 | extension JSON.UnkeyedDecoder 21 | { 22 | public 23 | var count:Int? 24 | { 25 | self.elements.count 26 | } 27 | public 28 | var isAtEnd:Bool 29 | { 30 | self.currentIndex >= self.elements.endIndex 31 | } 32 | 33 | mutating 34 | func diagnose(_ decode:(JSON.Node) throws -> T?) throws -> T 35 | { 36 | let key:Index = .init(intValue: self.currentIndex) 37 | var path:[any CodingKey] 38 | { 39 | self.codingPath + CollectionOfOne.init(key) 40 | } 41 | 42 | if self.isAtEnd 43 | { 44 | let context:DecodingError.Context = .init(codingPath: path, 45 | debugDescription: "index (\(self.currentIndex)) out of range") 46 | throw DecodingError.keyNotFound(key, context) 47 | } 48 | 49 | let value:JSON.Node = self.elements[self.currentIndex] 50 | self.currentIndex += 1 51 | do 52 | { 53 | if let decoded:T = try decode(value) 54 | { 55 | return decoded 56 | } 57 | 58 | throw DecodingError.init(annotating: JSON.TypecastError.init(invalid: value), 59 | initializing: T.self, 60 | path: path) 61 | } 62 | catch let error 63 | { 64 | throw DecodingError.init(annotating: error, 65 | initializing: T.self, 66 | path: path) 67 | } 68 | } 69 | } 70 | 71 | extension JSON.UnkeyedDecoder:UnkeyedDecodingContainer 72 | { 73 | public mutating 74 | func decode(_:T.Type) throws -> T where T:Decodable 75 | { 76 | try .init(from: try self.singleValueContainer()) 77 | } 78 | public mutating 79 | func decodeNil() throws -> Bool 80 | { 81 | try self.diagnose { $0.as(Never?.self) != nil } 82 | } 83 | public mutating 84 | func decode(_:Bool.Type) throws -> Bool 85 | { 86 | try self.diagnose { $0.as(Bool.self) } 87 | } 88 | public mutating 89 | func decode(_:Float.Type) throws -> Float 90 | { 91 | try self.diagnose { $0.as(Float.self) } 92 | } 93 | public mutating 94 | func decode(_:Double.Type) throws -> Double 95 | { 96 | try self.diagnose { $0.as(Double.self) } 97 | } 98 | public mutating 99 | func decode(_:String.Type) throws -> String 100 | { 101 | try self.diagnose { $0.as(String.self) } 102 | } 103 | public mutating 104 | func decode(_:Int.Type) throws -> Int 105 | { 106 | try self.diagnose { try $0.as(Int.self) } 107 | } 108 | public mutating 109 | func decode(_:Int64.Type) throws -> Int64 110 | { 111 | try self.diagnose { try $0.as(Int64.self) } 112 | } 113 | public mutating 114 | func decode(_:Int32.Type) throws -> Int32 115 | { 116 | try self.diagnose { try $0.as(Int32.self) } 117 | } 118 | public mutating 119 | func decode(_:Int16.Type) throws -> Int16 120 | { 121 | try self.diagnose { try $0.as(Int16.self) } 122 | } 123 | public mutating 124 | func decode(_:Int8.Type) throws -> Int8 125 | { 126 | try self.diagnose { try $0.as(Int8.self) } 127 | } 128 | public mutating 129 | func decode(_:UInt.Type) throws -> UInt 130 | { 131 | try self.diagnose { try $0.as(UInt.self) } 132 | } 133 | public mutating 134 | func decode(_:UInt64.Type) throws -> UInt64 135 | { 136 | try self.diagnose { try $0.as(UInt64.self) } 137 | } 138 | public mutating 139 | func decode(_:UInt32.Type) throws -> UInt32 140 | { 141 | try self.diagnose { try $0.as(UInt32.self) } 142 | } 143 | public mutating 144 | func decode(_:UInt16.Type) throws -> UInt16 145 | { 146 | try self.diagnose { try $0.as(UInt16.self) } 147 | } 148 | public mutating 149 | func decode(_:UInt8.Type) throws -> UInt8 150 | { 151 | try self.diagnose { try $0.as(UInt8.self) } 152 | } 153 | 154 | public mutating 155 | func superDecoder() throws -> any Decoder 156 | { 157 | try self.singleValueContainer() as any Decoder 158 | } 159 | public mutating 160 | func singleValueContainer() throws -> JSON.SingleValueDecoder 161 | { 162 | let key:Index = .init(intValue: self.currentIndex) 163 | let value:JSON.Node = try self.diagnose { $0 } 164 | let decoder:JSON.SingleValueDecoder = .init(value, 165 | path: self.codingPath + CollectionOfOne.init(key)) 166 | return decoder 167 | } 168 | public mutating 169 | func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer 170 | { 171 | let path:[any CodingKey] = self.codingPath + 172 | CollectionOfOne.init(Index.init(intValue: self.currentIndex)) 173 | let container:JSON.UnkeyedDecoder = 174 | .init(try self.diagnose(\.array), path: path) 175 | return container as any UnkeyedDecodingContainer 176 | } 177 | public mutating 178 | func nestedContainer(keyedBy _:NestedKey.Type) 179 | throws -> KeyedDecodingContainer 180 | { 181 | let path:[any CodingKey] = self.codingPath + 182 | CollectionOfOne.init(Index.init(intValue: self.currentIndex)) 183 | let container:JSON.KeyedDecoder = 184 | .init(try .init(indexing: try self.diagnose(\.object)), path: path) 185 | return .init(container) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/DecodingError (ext).swift: -------------------------------------------------------------------------------- 1 | extension DecodingError 2 | { 3 | init(annotating error:any Error, initializing _:T.Type, path:[any CodingKey]) 4 | { 5 | let description:String = 6 | """ 7 | initializer for type '\(String.init(reflecting: T.self))' \ 8 | threw an error while validating bson value at coding path \(path) 9 | """ 10 | let context:DecodingError.Context = .init(codingPath: path, 11 | debugDescription: description, underlyingError: error) 12 | self = .dataCorrupted(context) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/JSONLegacy/exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import struct JSONAST.JSON 2 | -------------------------------------------------------------------------------- /Sources/JSONParsing/JSON.Array (ext).swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | import JSONAST 3 | 4 | extension JSON.Array 5 | { 6 | /// Attempts to parse a JSON array from raw UTF-8 JSON data. 7 | public 8 | init(parsing json:JSON) throws 9 | { 10 | self.init(try JSON.NodeRule.Array.parse(json.utf8)) 11 | } 12 | /// Attempts to parse a JSON array from a string. 13 | public 14 | init(parsing string:String) throws 15 | { 16 | self.init(try JSON.NodeRule.Array.parse(string.utf8)) 17 | } 18 | /// Attempts to parse a JSON array from a substring. 19 | public 20 | init(parsing string:Substring) throws 21 | { 22 | self.init(try JSON.NodeRule.Array.parse(string.utf8)) 23 | } 24 | } 25 | extension JSON.Array:LosslessStringConvertible 26 | { 27 | /// See ``init(parsing:) (String)``. 28 | public 29 | init?(_ description:String) 30 | { 31 | do { try self.init(parsing: description) } 32 | catch { return nil } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/JSONParsing/JSON.InvalidUnicodeScalarError.swift: -------------------------------------------------------------------------------- 1 | extension JSON 2 | { 3 | /// A string literal contained a unicode escape sequence that does not encode a 4 | /// valid ``Unicode/Scalar``. 5 | /// 6 | /// This error is thrown by the parser. Decoders should not use it. 7 | public 8 | struct InvalidUnicodeScalarError:Error, Equatable, Sendable 9 | { 10 | public 11 | let value:UInt16 12 | @inlinable public 13 | init(value:UInt16) 14 | { 15 | self.value = value 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/JSONParsing/JSON.Node (ext).swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | import JSONAST 3 | 4 | extension JSON.Node 5 | { 6 | /// Attempts to parse a complete JSON message (either an ``Array`` or an 7 | /// ``Object``) from raw UTF-8 JSON data. 8 | public 9 | init(parsing json:JSON) throws 10 | { 11 | self = try JSON.RootRule.parse(json.utf8) 12 | } 13 | /// Attempts to parse a complete JSON message (either an ``Array`` or an 14 | /// ``Object``) from a string. 15 | public 16 | init(parsing string:String) throws 17 | { 18 | self = try JSON.RootRule.parse(string.utf8) 19 | } 20 | /// Attempts to parse a complete JSON message (either an ``Array`` or an 21 | /// ``Object``) from a substring. 22 | public 23 | init(parsing string:Substring) throws 24 | { 25 | self = try JSON.RootRule.parse(string.utf8) 26 | } 27 | 28 | /// Attempts to parse a a JSON fragment from a string. 29 | public 30 | init(parsingFragment string:String) throws 31 | { 32 | self = try JSON.NodeRule.parse(string.utf8) 33 | } 34 | /// Attempts to parse a a JSON fragment from a substring. 35 | public 36 | init(parsingFragment string:Substring) throws 37 | { 38 | self = try JSON.NodeRule.parse(string.utf8) 39 | } 40 | } 41 | extension JSON.Node:LosslessStringConvertible 42 | { 43 | /// See ``init(parsingFragment:) (String)``. 44 | public 45 | init?(_ description:String) 46 | { 47 | do { try self.init(parsingFragment: description) } 48 | catch { return nil } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/JSONParsing/JSON.Object (ext).swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | import JSONAST 3 | 4 | extension JSON.Object 5 | { 6 | /// Attempts to parse a JSON object from raw UTF-8 JSON data. 7 | /// 8 | /// > Note: 9 | /// Unlike BSON lists, you cannot reparse JSON arrays as objects. 10 | public 11 | init(parsing json:JSON) throws 12 | { 13 | self.init(try JSON.NodeRule.Object.parse(json.utf8)) 14 | } 15 | /// Attempts to parse a JSON object from a string. 16 | /// 17 | /// > Note: 18 | /// Unlike BSON lists, you cannot reparse JSON arrays as objects. 19 | public 20 | init(parsing string:String) throws 21 | { 22 | self.init(try JSON.NodeRule.Object.parse(string.utf8)) 23 | } 24 | /// Attempts to parse a JSON object from a substring. 25 | /// 26 | /// > Note: 27 | /// Unlike BSON lists, you cannot reparse JSON arrays as objects. 28 | public 29 | init(parsing string:Substring) throws 30 | { 31 | self.init(try JSON.NodeRule.Object.parse(string.utf8)) 32 | } 33 | } 34 | extension JSON.Object:LosslessStringConvertible 35 | { 36 | /// See ``init(parsing:) (String)``. 37 | public 38 | init?(_ description:String) 39 | { 40 | do { try self.init(parsing: description) } 41 | catch { return nil } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON (ext).swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON 4 | { 5 | typealias CommaRule = 6 | Pattern.Pad.Comma, WhitespaceRule> 7 | 8 | typealias BracketLeftRule = 9 | Pattern.Pad.BracketLeft, WhitespaceRule> 10 | 11 | typealias BracketRightRule = 12 | Pattern.Pad.BracketRight, WhitespaceRule> 13 | 14 | typealias BraceLeftRule = 15 | Pattern.Pad.BraceLeft, WhitespaceRule> 16 | 17 | typealias ColonRule = 18 | Pattern.Pad.Colon, WhitespaceRule> 19 | 20 | typealias BraceRightRule = 21 | Pattern.Pad.BraceRight, WhitespaceRule> 22 | } 23 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NodeRule.Array.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.NodeRule 4 | { 5 | /// Matches an array literal. 6 | /// 7 | /// Array literals begin and end with square brackets (`[` and `]`), and 8 | /// recursively contain instances of ``JSON.NodeRule`` separated by ``JSON.CommaRule``s. 9 | /// Trailing commas (a JSON5 extension) are not allowed. 10 | enum Array 11 | { 12 | } 13 | } 14 | extension JSON.NodeRule.Array:ParsingRule 15 | { 16 | typealias Terminal = UInt8 17 | 18 | static 19 | func parse( 20 | _ input:inout ParsingInput>) throws -> [JSON.Node] 21 | where Source.Element == Terminal, 22 | Source.Index == Location 23 | { 24 | try input.parse(as: JSON.BracketLeftRule.self) 25 | var elements:[JSON.Node] 26 | if let head:JSON.Node = try? input.parse(as: JSON.NodeRule.self) 27 | { 28 | elements = [head] 29 | while let (_, next):(Void, JSON.Node) = try? input.parse( 30 | as: (JSON.CommaRule, JSON.NodeRule).self) 31 | { 32 | elements.append(next) 33 | } 34 | } 35 | else 36 | { 37 | elements = [] 38 | } 39 | try input.parse(as: JSON.BracketRightRule.self) 40 | return elements 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NodeRule.False.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.NodeRule 4 | { 5 | /// A literal `false` expression. 6 | enum False:LiteralRule 7 | { 8 | typealias Terminal = UInt8 9 | 10 | /// The ASCII string `false`. 11 | static 12 | var literal:[UInt8] { [0x66, 0x61, 0x6c, 0x73, 0x65] } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NodeRule.Null.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.NodeRule 4 | { 5 | /// A literal `null` expression. 6 | enum Null:LiteralRule 7 | { 8 | typealias Terminal = UInt8 9 | 10 | /// The ASCII string `null`. 11 | static 12 | var literal:[UInt8] { [0x6e, 0x75, 0x6c, 0x6c] } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NodeRule.Object.Item.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.NodeRule.Object 4 | { 5 | /// Matches an key-value expression. 6 | /// 7 | /// A key-value expression consists of a ``JSON.StringRule``, a ``JSON.ColonRule``, and 8 | /// a recursive instance of ``JSON.NodeRule``. 9 | enum Item 10 | { 11 | } 12 | } 13 | extension JSON.NodeRule.Object.Item:ParsingRule 14 | { 15 | typealias Terminal = UInt8 16 | 17 | static 18 | func parse( 19 | _ input:inout ParsingInput>) throws -> 20 | ( 21 | key:JSON.Key, 22 | value:JSON.Node 23 | ) 24 | where Source.Index == Location, Source.Element == Terminal 25 | { 26 | let key:String = try input.parse(as: JSON.StringRule.self) 27 | try input.parse(as: JSON.ColonRule.self) 28 | let value:JSON.Node = try input.parse(as: JSON.NodeRule.self) 29 | return (.init(rawValue: key), value) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NodeRule.Object.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.NodeRule 4 | { 5 | /// Matches an object literal. 6 | /// 7 | /// Object literals begin and end with curly braces (`{` and `}`), and 8 | /// contain instances of ``Item`` separated by ``JSON.CommaRule``s. 9 | /// Trailing commas are not allowed. 10 | enum Object 11 | { 12 | } 13 | } 14 | extension JSON.NodeRule.Object:ParsingRule 15 | { 16 | typealias Terminal = UInt8 17 | 18 | static 19 | func parse(_ input:inout ParsingInput>) 20 | throws -> [(key:JSON.Key, value:JSON.Node)] 21 | where Source.Index == Location, Source.Element == Terminal 22 | { 23 | try input.parse(as: JSON.BraceLeftRule.self) 24 | var items:[(key:JSON.Key, value:JSON.Node)] 25 | if let head:(key:JSON.Key, value:JSON.Node) = try? input.parse(as: Item.self) 26 | { 27 | items = [head] 28 | while let (_, next):(Void, (key:JSON.Key, value:JSON.Node)) = try? input.parse( 29 | as: (JSON.CommaRule, Item).self) 30 | { 31 | items.append(next) 32 | } 33 | } 34 | else 35 | { 36 | items = [] 37 | } 38 | try input.parse(as: JSON.BraceRightRule.self) 39 | return items 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NodeRule.True.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.NodeRule 4 | { 5 | /// A literal `true` expression. 6 | enum True:LiteralRule 7 | { 8 | typealias Terminal = UInt8 9 | 10 | /// The ASCII string `true`. 11 | static 12 | var literal:[UInt8] { [0x74, 0x72, 0x75, 0x65] } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NodeRule.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON 4 | { 5 | /// Matches any value, including fragment values. 6 | /// 7 | /// Only use this if you are doing manual JSON parsing. Most web services 8 | /// should send complete ``JSON.RootRule`` messages through their public APIs. 9 | enum NodeRule 10 | { 11 | } 12 | } 13 | extension JSON.NodeRule:ParsingRule 14 | { 15 | typealias Terminal = UInt8 16 | 17 | static 18 | func parse( 19 | _ input:inout ParsingInput>) throws -> JSON.Node 20 | where Source.Element == Terminal, 21 | Source.Index == Location 22 | { 23 | if let number:JSON.Number = input.parse(as: JSON.NumberRule?.self) 24 | { 25 | return .number(number) 26 | } 27 | else if 28 | let string:String = input.parse(as: JSON.StringRule?.self) 29 | { 30 | return .string(JSON.Literal.init(string)) 31 | } 32 | else if 33 | let items:[(JSON.Key, JSON.Node)] = input.parse(as: Object?.self) 34 | { 35 | return .object(.init(items)) 36 | } 37 | else if 38 | let elements:[JSON.Node] = input.parse(as: Array?.self) 39 | { 40 | return .array(.init(elements)) 41 | } 42 | else if 43 | let _:Void = input.parse(as: True?.self) 44 | { 45 | return .bool(true) 46 | } 47 | else if 48 | let _:Void = input.parse(as: False?.self) 49 | { 50 | return .bool(false) 51 | } 52 | else 53 | { 54 | try input.parse(as: Null.self) 55 | return .null 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NumberRule.PlusOrMinus.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.NumberRule 4 | { 5 | /// Matches an ASCII `+` or `-` sign. 6 | enum PlusOrMinus:TerminalRule 7 | { 8 | typealias Terminal = UInt8 9 | typealias Construction = FloatingPointSign 10 | 11 | static 12 | func parse(terminal:UInt8) -> FloatingPointSign? 13 | { 14 | switch terminal 15 | { 16 | case 0x2b: .plus 17 | case 0x2d: .minus 18 | default: nil 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.NumberRule.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON 4 | { 5 | /// Matches a numeric literal. 6 | /// 7 | /// Numeric literals are always written in decimal. 8 | /// 9 | /// The following examples are all valid literals: 10 | /** 11 | ```swift 12 | "5" 13 | "5.5" 14 | "-5.5" 15 | "-55e-2" 16 | "-55e2" 17 | "-55e+2" 18 | ``` 19 | */ 20 | /// Numeric literals may not begin with a prefix `+` sign, although the 21 | /// exponent field can use a prefix `+`. 22 | enum NumberRule 23 | { 24 | } 25 | } 26 | extension JSON.NumberRule:ParsingRule 27 | { 28 | typealias Terminal = UInt8 29 | 30 | static 31 | func parse( 32 | _ input:inout ParsingInput>) throws -> JSON.Number 33 | where Source.Element == Terminal, 34 | Source.Index == Location 35 | { 36 | /// ASCII decimal digit terminals. 37 | typealias DecimalDigit = UnicodeDigit.Decimal 38 | where T:BinaryInteger 39 | /// ASCII terminals. 40 | typealias ASCII = UnicodeEncoding 41 | 42 | // https://datatracker.ietf.org/doc/html/rfc8259#section-6 43 | // JSON does not allow prefix '+' 44 | let sign:FloatingPointSign 45 | switch input.parse(as: ASCII.Hyphen?.self) 46 | { 47 | case _?: sign = .minus 48 | case nil: sign = .plus 49 | } 50 | 51 | var units:UInt64 = 52 | try input.parse(as: Pattern.UnsignedInteger>.self) 53 | var places:UInt32 = 0 54 | if var (_, remainder):(Void, UInt64) = 55 | try? input.parse(as: (ASCII.Period, DecimalDigit).self) 56 | { 57 | while true 58 | { 59 | if case (let shifted, false) = units.multipliedReportingOverflow(by: 10), 60 | case (let refined, false) = shifted.addingReportingOverflow(remainder) 61 | { 62 | places += 1 63 | units = refined 64 | } 65 | else 66 | { 67 | throw Pattern.IntegerOverflowError.init() 68 | } 69 | 70 | if let next:UInt64 = input.parse(as: DecimalDigit?.self) 71 | { 72 | remainder = next 73 | } 74 | else 75 | { 76 | break 77 | } 78 | } 79 | } 80 | if let _:Void = input.parse(as: ASCII.E?.self) 81 | { 82 | let sign:FloatingPointSign? = input.parse(as: PlusOrMinus?.self) 83 | let exponent:UInt32 = try input.parse( 84 | as: Pattern.UnsignedInteger>.self) 85 | // you too, can exploit the vulnerabilities below 86 | switch sign 87 | { 88 | case .minus?: 89 | places += exponent 90 | 91 | case .plus?, nil: 92 | guard places < exponent 93 | else 94 | { 95 | places -= exponent 96 | break 97 | } 98 | defer 99 | { 100 | places = 0 101 | } 102 | guard units != 0 103 | else 104 | { 105 | break 106 | } 107 | let shift:Int = .init(exponent - places) 108 | if shift < JSON.Number.Base10.Exp.endIndex, 109 | case (let shifted, false) = units.multipliedReportingOverflow( 110 | by: JSON.Number.Base10.Exp[shift]) 111 | { 112 | units = shifted 113 | } 114 | else 115 | { 116 | throw Pattern.IntegerOverflowError.init() 117 | } 118 | } 119 | } 120 | return .init(sign: sign, units: units, places: places) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.RootRule.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON 4 | { 5 | /// All of the parsing rules in this library are defined at the UTF-8 level. 6 | /// 7 | /// To parse *any* JSON value, including fragment values, use ``JSON.NodeRule`` instead. 8 | /// 9 | /// You can parse JSON expressions from any ``Collection`` with an 10 | /// ``Collection Element`` type of ``UInt8``. For example, you can parse 11 | /// a ``String`` through its ``String UTF8View``. 12 | /** 13 | ```swift 14 | let string:String = 15 | """ 16 | {"success":true,"value":0.1} 17 | """ 18 | try JSON.RootRule.parse(string.utf8) 19 | ``` 20 | */ 21 | /// However, you could also parse a UTF-8 buffer directly, without 22 | /// having to convert it to a ``String``. 23 | /** 24 | ```swift 25 | let utf8:[UInt8] = 26 | [ 27 | 123, 34, 115, 117, 99, 99, 101, 115, 28 | 115, 34, 58, 116, 114, 117, 101, 44, 29 | 34, 118, 97, 108, 117, 101, 34, 58, 30 | 48, 46, 49, 125 31 | ] 32 | try JSON.RootRule.Index>.parse(utf8) 33 | ``` 34 | */ 35 | /// The generic [`Location`]() 36 | /// parameter provides this flexibility as a zero-cost abstraction. 37 | /// 38 | /// > Tip: 39 | /// The ``/swift-grammar`` and ``/swift-json`` libraries are transparent! 40 | /// This means that its parsing rules are always zero-cost abstractions, 41 | /// even when applied to third-party collection types, like 42 | /// ``/swift-nio/NIOCore/ByteBufferView``. 43 | enum RootRule 44 | { 45 | } 46 | } 47 | extension JSON.RootRule:ParsingRule 48 | { 49 | typealias Terminal = UInt8 50 | 51 | static 52 | func parse( 53 | _ input:inout ParsingInput>) throws -> JSON.Node 54 | where Source.Element == Terminal, 55 | Source.Index == Location 56 | { 57 | if let items:[(JSON.Key, JSON.Node)] = input.parse( 58 | as: JSON.NodeRule.Object?.self) 59 | { 60 | return .object(.init(items)) 61 | } 62 | else 63 | { 64 | return .array(.init(try input.parse( 65 | as: JSON.NodeRule.Array.self))) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.StringRule.CodeUnit.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.StringRule 4 | { 5 | /// Matches a UTF-8 code unit that is allowed to appear inline in a string literal. 6 | enum CodeUnit:TerminalRule 7 | { 8 | typealias Terminal = UInt8 9 | typealias Construction = Void 10 | 11 | static 12 | func parse(terminal:UInt8) -> Void? 13 | { 14 | switch terminal 15 | { 16 | case 0x20 ... 0x21, 0x23 ... 0x5b, 0x5d ... 0xff: () 17 | default: nil 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.StringRule.EscapeSequence.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.StringRule 4 | { 5 | /// Matches a sequence of escaped UTF-16 code units. 6 | /// 7 | /// A UTF-16 escape sequence consists of `\u`, followed by four hexadecimal digits. 8 | enum EscapeSequence 9 | { 10 | } 11 | } 12 | extension JSON.StringRule.EscapeSequence:ParsingRule 13 | { 14 | typealias Terminal = UInt8 15 | 16 | static 17 | func parse( 18 | _ input:inout ParsingInput>) throws -> String 19 | where Source.Element == Terminal, 20 | Source.Index == Location 21 | { 22 | typealias HexDigit = UnicodeDigit.Hex 23 | typealias ASCII = UnicodeEncoding 24 | 25 | try input.parse(as: ASCII.Backslash.self) 26 | var unescaped:String = "" 27 | while true 28 | { 29 | if let scalar:Unicode.Scalar = input.parse( 30 | as: JSON.StringRule.EscapedCodeUnit?.self) 31 | { 32 | unescaped.append(Character.init(scalar)) 33 | } 34 | else 35 | { 36 | try input.parse(as: ASCII.LowercaseU.self) 37 | let value:UInt16 = 38 | (try input.parse(as: HexDigit.self) << 12) | 39 | (try input.parse(as: HexDigit.self) << 8) | 40 | (try input.parse(as: HexDigit.self) << 4) | 41 | (try input.parse(as: HexDigit.self)) 42 | if let scalar:Unicode.Scalar = Unicode.Scalar.init(value) 43 | { 44 | unescaped.append(Character.init(scalar)) 45 | } 46 | else 47 | { 48 | throw JSON.InvalidUnicodeScalarError.init(value: value) 49 | } 50 | } 51 | 52 | guard case _? = input.parse(as: ASCII.Backslash?.self) 53 | else 54 | { 55 | break 56 | } 57 | } 58 | return unescaped 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.StringRule.EscapedCodeUnit.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON.StringRule 4 | { 5 | /// Matches an ASCII character (besides `u`) that is allowed to 6 | /// appear immediately after a backslash (`\`) in a string literal. 7 | /// 8 | /// The following are valid escape characters: `\`, `"`, `/`, `b`, `f`, `n`, `r`, `t`. 9 | enum EscapedCodeUnit:TerminalRule 10 | { 11 | typealias Terminal = UInt8 12 | typealias Construction = Unicode.Scalar 13 | 14 | static 15 | func parse(terminal:UInt8) -> Unicode.Scalar? 16 | { 17 | switch terminal 18 | { 19 | // '\\', '\"', '\/' 20 | case 0x5c, 0x22, 0x2f: 21 | .init(terminal) 22 | case 0x62: "\u{08}" // '\b' 23 | case 0x66: "\u{0C}" // '\f' 24 | case 0x6e: "\u{0A}" // '\n' 25 | case 0x72: "\u{0D}" // '\r' 26 | case 0x74: "\u{09}" // '\t' 27 | default: nil 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.StringRule.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON 4 | { 5 | /// Matches a string literal. 6 | /// 7 | /// String literals always begin and end with an ASCII double quote character (`"`). 8 | enum StringRule 9 | { 10 | } 11 | } 12 | extension JSON.StringRule:ParsingRule 13 | { 14 | typealias Terminal = UInt8 15 | 16 | static 17 | func parse( 18 | _ input:inout ParsingInput>) throws -> String 19 | where Source.Element == Terminal, 20 | Source.Index == Location 21 | { 22 | typealias DoubleQuote = UnicodeEncoding.DoubleQuote 23 | 24 | try input.parse(as: DoubleQuote.self) 25 | 26 | let start:Location = input.index 27 | input.parse(as: CodeUnit.self, in: Void.self) 28 | let end:Location = input.index 29 | var string:String = .init(decoding: input[start ..< end], as: Unicode.UTF8.self) 30 | 31 | while let next:String = input.parse(as: EscapeSequence?.self) 32 | { 33 | string += next 34 | let start:Location = input.index 35 | input.parse(as: CodeUnit.self, in: Void.self) 36 | let end:Location = input.index 37 | string += .init(decoding: input[start ..< end], as: Unicode.UTF8.self) 38 | } 39 | 40 | try input.parse(as: DoubleQuote.self) 41 | return string 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/JSONParsing/Rules/JSON.WhitespaceRule.swift: -------------------------------------------------------------------------------- 1 | import Grammar 2 | 3 | extension JSON 4 | { 5 | /// Matches the whitespace characters U+0020, `\t`, `\n`, and `\r`. 6 | /// 7 | /// This rule matches a *single* whitespace character. 8 | /// To match a sequence of whitespace characters (including the empty sequence), 9 | /// use one of `swift-grammar`’s vector parsing APIs, like ``ParsingInput.parse(as:in:)``. 10 | /// 11 | /// For example, the following is equivalent to the regex `/[\ \t\n\r]+/`: 12 | /** 13 | ```swift 14 | try input.parse(as: JSON.WhitespaceRule.self) 15 | input.parse(as: JSON.WhitespaceRule.self, in: Void.self) 16 | ``` 17 | */ 18 | /// > Note: Unicode space characters, like U+2009, are not 19 | /// considered whitespace characters in the context of JSON parsing. 20 | enum WhitespaceRule:TerminalRule 21 | { 22 | typealias Terminal = UInt8 23 | typealias Construction = Void 24 | 25 | static 26 | func parse(terminal:UInt8) -> Void? 27 | { 28 | switch terminal 29 | { 30 | case 0x20, // ' ' 31 | 0x09, // '\t' 32 | 0x0a, // '\n' 33 | 0x0d: // '\r' 34 | () 35 | default: 36 | nil 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/JSONParsing/exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import struct JSONAST.JSON 2 | -------------------------------------------------------------------------------- /Sources/JSONTests/IntegerOverflow.swift: -------------------------------------------------------------------------------- 1 | import JSON 2 | import Testing 3 | 4 | @Suite 5 | enum IntegerOverflow 6 | { 7 | @Test 8 | static func AsInt8() 9 | { 10 | Self.expect(256, overflows: Int8.self) 11 | } 12 | 13 | @Test 14 | static func AsInt16() 15 | { 16 | Self.decode(256, to: Int16.self) 17 | } 18 | 19 | @Test 20 | static func AsInt32() 21 | { 22 | Self.decode(256, to: Int32.self) 23 | } 24 | 25 | @Test 26 | static func AsInt64() 27 | { 28 | Self.decode(256, to: Int64.self) 29 | } 30 | 31 | @Test 32 | static func AsInt64Max() 33 | { 34 | Self.decode(Int64.max, to: Int64.self) 35 | } 36 | 37 | @Test 38 | static func AsInt64Min() 39 | { 40 | Self.decode(Int64.min, to: Int64.self) 41 | } 42 | 43 | @Test 44 | static func AsInt() 45 | { 46 | Self.decode(256, to: Int.self) 47 | } 48 | 49 | @Test 50 | static func AsUInt8() 51 | { 52 | Self.expect(256, overflows: UInt8.self) 53 | } 54 | 55 | @Test 56 | static func AsUInt16() 57 | { 58 | Self.decode(256, to: UInt16.self) 59 | } 60 | 61 | @Test 62 | static func AsUInt32() 63 | { 64 | Self.decode(256, to: UInt32.self) 65 | } 66 | 67 | @Test 68 | static func AsUInt64() 69 | { 70 | Self.decode(256, to: UInt64.self) 71 | } 72 | 73 | @Test 74 | static func AsUInt64Max() 75 | { 76 | Self.decode(UInt64.max, to: UInt64.self) 77 | } 78 | 79 | @Test 80 | static func AsUInt() 81 | { 82 | Self.decode(256, to: UInt.self) 83 | } 84 | } 85 | extension IntegerOverflow 86 | { 87 | private 88 | static func expect(_ value:Int64, overflows:Signed.Type) 89 | where Signed:SignedInteger & JSONDecodable 90 | { 91 | let field:JSON.FieldDecoder = .init(key: nil, 92 | value: .number(.init(value))) 93 | 94 | #expect(throws: JSON.DecodingError.self) 95 | { 96 | let _:Signed = try field.decode() 97 | } 98 | } 99 | private 100 | static func decode(_ value:Int64, to:Signed.Type) 101 | where Signed:SignedInteger & JSONDecodable 102 | { 103 | let field:JSON.FieldDecoder = .init(key: nil, 104 | value: .number(.init(value))) 105 | 106 | #expect(throws: Never.self) 107 | { 108 | let _:Signed = try field.decode() 109 | } 110 | } 111 | 112 | private 113 | static func expect(_ value:UInt64, overflows:Unsigned.Type) 114 | where Unsigned:UnsignedInteger & JSONDecodable 115 | { 116 | let field:JSON.FieldDecoder = .init(key: nil, 117 | value: .number(.init(value))) 118 | 119 | #expect(throws: JSON.DecodingError.self) 120 | { 121 | let _:Unsigned = try field.decode() 122 | } 123 | } 124 | private 125 | static func decode(_ value:UInt64, to:Unsigned.Type) 126 | where Unsigned:UnsignedInteger & JSONDecodable 127 | { 128 | let field:JSON.FieldDecoder = .init(key: nil, 129 | value: .number(.init(value))) 130 | 131 | #expect(throws: Never.self) 132 | { 133 | let _:Unsigned = try field.decode() 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Sources/JSONTests/Parsing.swift: -------------------------------------------------------------------------------- 1 | import JSON 2 | import Testing 3 | 4 | @Suite 5 | enum Parsing 6 | { 7 | @Test 8 | static func Null() throws 9 | { 10 | guard case JSON.Node.null? = try .init(parsingFragment: "null") 11 | else 12 | { 13 | Issue.record() 14 | return 15 | } 16 | } 17 | 18 | @Test 19 | static func BoolTrue() throws 20 | { 21 | guard case JSON.Node.bool(true)? = try .init(parsingFragment: "true") 22 | else 23 | { 24 | Issue.record() 25 | return 26 | } 27 | } 28 | @Test 29 | static func BoolFalse() throws 30 | { 31 | guard case JSON.Node.bool(false)? = try .init(parsingFragment: "false") 32 | else 33 | { 34 | Issue.record() 35 | return 36 | } 37 | } 38 | 39 | @Test 40 | static func Number0() throws 41 | { 42 | guard case JSON.Node.number(.init(sign: .plus, units: 0, places: 0))? = try .init( 43 | parsingFragment: "0") 44 | else 45 | { 46 | Issue.record() 47 | return 48 | } 49 | } 50 | 51 | @Test 52 | static func String() throws 53 | { 54 | guard case JSON.Node.string(.init("a"))? = try .init(parsingFragment: "\"a\"") 55 | else 56 | { 57 | Issue.record() 58 | return 59 | } 60 | } 61 | } 62 | --------------------------------------------------------------------------------