Skip to content

Commit 3a613b0

Browse files
committed
Merge branch 'feature/json-serialization'
2 parents 1c64d68 + 00fdce8 commit 3a613b0

9 files changed

Lines changed: 3683 additions & 0 deletions

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ Depend on `ReerJSON` in your target.
5959
```
6060

6161
# Usage
62+
63+
## Decoder && Encoder
64+
6265
`ReerJSONDecoder` and `ReerJSONEncoder` are API-compatible replacements for Foundation's JSONDecoder and JSONEncoder.
6366
Simply swap the type and add the import, no other code changes required:
6467

@@ -76,6 +79,74 @@ let encoder = ReerJSONEncoder()
7679

7780
All public interfaces, behaviors, error types, and coding strategies are identical to the Foundation counterparts. The ReerJSON test suite includes exhaustive test cases covering every feature, ensuring full compatibility.
7881

82+
## DOM-Style Access
83+
84+
Parse JSON and access values directly without defining types:
85+
86+
```swift
87+
import ReerJSON
88+
89+
let json = #"{"users": [{"name": "Alice"}, {"name": "Bob"}]}"#
90+
let value = try JSONValue(string: json)
91+
92+
// Access nested values with subscripts
93+
if let name = value["users"]?[0]?["name"]?.string {
94+
print(name) // "Alice"
95+
}
96+
```
97+
98+
## In-Place Parsing
99+
100+
For maximum performance with large JSON files,
101+
use in-place parsing to avoid copying the input data:
102+
103+
```swift
104+
var data = try Data(contentsOf: fileURL)
105+
let json = try JSONValue.parseInPlace(consuming: &data)
106+
// `data` is now consumed and should not be used
107+
```
108+
109+
In-place parsing allows yyjson to parse directly within the input buffer,
110+
avoiding memory allocation for string storage.
111+
The `inout` parameter makes it clear that the data is consumed by this operation.
112+
113+
> [!NOTE]
114+
> For most use cases, the standard `YYJSONValue(data:)` initializer is sufficient.
115+
> Use in-place parsing only when performance is critical
116+
> and you can accept the ownership semantics.
117+
118+
## JSONSerialization Alternative
119+
120+
Use `ReerJSONSerialization` with the same API as Foundation's `JSONSerialization`:
121+
122+
```swift
123+
import ReerJSON
124+
125+
let json = #"{"message": "Hello, World!"}"#
126+
let data = json.data(using: .utf8)!
127+
128+
let object = try ReerJSONSerialization.jsonObject(with: data)
129+
if let dict = object as? [String: Any] {
130+
print(dict["message"] as? String ?? "") // "Hello, World!"
131+
}
132+
```
133+
134+
Configure output formatting with `WritingOptions`:
135+
136+
```swift
137+
// Pretty printing with 2-space indent (useful for Xcode asset catalogs)
138+
let data = try ReerJSONSerialization.data(
139+
withJSONObject: dict,
140+
options: [.indentationTwoSpaces, .sortedKeys]
141+
)
142+
143+
// ASCII-only output with trailing newline
144+
let data = try ReerJSONSerialization.data(
145+
withJSONObject: dict,
146+
options: [.escapeUnicode, .newlineAtEnd]
147+
)
148+
```
149+
79150

80151
# Differences
81152

@@ -108,6 +179,7 @@ Portions of this project incorporate code from the following source code or test
108179

109180
* [swiftlang/swift-foundation](https://github.com/swiftlang/swift-foundation), licensed under the Apache License, Version 2.0.
110181
* [michaeleisel/ZippyJSON](https://github.com/michaeleisel/ZippyJSON), licensed under the MIT License.
182+
* [mattt/swift-yyjson](https://github.com/mattt/swift-yyjson), licensed under the MIT License. The `ReerJSONSerialization`, `Value`, `Configuration`, `Error`, and `Helpers` modules are adapted from this project.
111183

112184
See the LICENSE file for the full text of both licenses.
113185

@@ -119,6 +191,7 @@ We would like to express our gratitude to the following projects and their contr
119191
* **[swiftlang/swift-foundation](https://github.com/swiftlang/swift-foundation)** - For implementation reference and comprehensive test suites that helped ensure compatibility.
120192
* **[michaeleisel/ZippyJSON](https://github.com/michaeleisel/ZippyJSON)** - For the innovative Swift JSON parsing approach and valuable test cases.
121193
* **[michaeleisel/JJLISO8601DateFormatter](https://github.com/michaeleisel/JJLISO8601DateFormatter)** - For the high-performance date formatting implementation.
194+
* **[mattt/swift-yyjson](https://github.com/mattt/swift-yyjson)** - For the `JSONSerialization` replacement and DOM-style `JSONValue`/`JSONDocument` APIs. The `ReerJSONSerialization`, `Value`, `Configuration`, `Error`, and `Helpers` source files and their tests are adapted from this project.
122195
* **[nixzhu/Ananda](https://github.com/nixzhu/Ananda)** - For the pioneering work in integrating yyjson with Swift and providing architectural inspiration.
123196

124197
Special thanks to all the open-source contributors who made this project possible.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//
2+
// Copyright © 2026 Mattt (https://github.com/mattt)
3+
// Copyright © 2026 reers.
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
22+
23+
import yyjson
24+
import Foundation
25+
26+
/// Options for reading JSON data.
27+
public struct JSONReadOptions: OptionSet, Sendable {
28+
public let rawValue: UInt32
29+
30+
public init(rawValue: UInt32) {
31+
self.rawValue = rawValue
32+
}
33+
34+
/// Default option (RFC 8259 compliant).
35+
public static let `default` = JSONReadOptions([])
36+
37+
/// Stops when done instead of issuing an error if there's additional content
38+
/// after a JSON document.
39+
public static let stopWhenDone = JSONReadOptions(rawValue: YYJSON_READ_STOP_WHEN_DONE)
40+
41+
/// Read all numbers as raw strings.
42+
public static let numberAsRaw = JSONReadOptions(rawValue: YYJSON_READ_NUMBER_AS_RAW)
43+
44+
/// Allow reading invalid unicode when parsing string values.
45+
public static let allowInvalidUnicode = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_INVALID_UNICODE)
46+
47+
/// Read big numbers as raw strings.
48+
public static let bigNumberAsRaw = JSONReadOptions(rawValue: YYJSON_READ_BIGNUM_AS_RAW)
49+
50+
/// Allow single trailing comma at the end of an object or array.
51+
public static let allowTrailingCommas = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_TRAILING_COMMAS)
52+
53+
/// Allow C-style single-line and multi-line comments.
54+
public static let allowComments = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_COMMENTS)
55+
56+
/// Allow inf/nan number and literal, case-insensitive.
57+
public static let allowInfAndNaN = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_INF_AND_NAN)
58+
59+
/// Allow UTF-8 BOM and skip it before parsing.
60+
public static let allowBOM = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_BOM)
61+
62+
/// Allow extended number formats (hex, leading/trailing decimal point, leading plus).
63+
public static let allowExtendedNumbers = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_EXT_NUMBER)
64+
65+
/// Allow extended escape sequences in strings.
66+
public static let allowExtendedEscapes = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_EXT_ESCAPE)
67+
68+
/// Allow extended whitespace characters.
69+
public static let allowExtendedWhitespace = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_EXT_WHITESPACE)
70+
71+
/// Allow strings enclosed in single quotes.
72+
public static let allowSingleQuotedStrings = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_SINGLE_QUOTED_STR)
73+
74+
/// Allow object keys without quotes.
75+
public static let allowUnquotedKeys = JSONReadOptions(rawValue: YYJSON_READ_ALLOW_UNQUOTED_KEY)
76+
77+
/// Allow JSON5 format.
78+
///
79+
/// This includes trailing commas, comments, inf/nan, extended numbers,
80+
/// extended escapes, extended whitespace, single-quoted strings, and unquoted keys.
81+
public static let json5 = JSONReadOptions(rawValue: YYJSON_READ_JSON5)
82+
83+
/// Convert to yyjson read flags.
84+
internal var yyjsonFlags: yyjson_read_flag {
85+
yyjson_read_flag(rawValue)
86+
}
87+
}
88+
89+
/// Options for writing JSON data.
90+
public struct JSONWriteOptions: OptionSet, Sendable {
91+
public let rawValue: UInt32
92+
93+
public init(rawValue: UInt32) {
94+
self.rawValue = rawValue
95+
}
96+
97+
/// Default option (minified output).
98+
public static let `default` = JSONWriteOptions([])
99+
100+
/// Write JSON pretty with 4 space indent.
101+
public static let prettyPrinted = JSONWriteOptions(rawValue: YYJSON_WRITE_PRETTY)
102+
103+
/// Write JSON pretty with 2 space indent (implies `prettyPrinted`).
104+
public static let indentationTwoSpaces = JSONWriteOptions(rawValue: YYJSON_WRITE_PRETTY_TWO_SPACES)
105+
106+
/// Escape unicode as `\uXXXX`, making the output ASCII only.
107+
public static let escapeUnicode = JSONWriteOptions(rawValue: YYJSON_WRITE_ESCAPE_UNICODE)
108+
109+
/// Escape '/' as '\/'.
110+
public static let escapeSlashes = JSONWriteOptions(rawValue: YYJSON_WRITE_ESCAPE_SLASHES)
111+
112+
/// Writes infinity and NaN values as `Infinity` and `NaN` literals.
113+
///
114+
/// If you set `infAndNaNAsNull`, it takes precedence.
115+
public static let allowInfAndNaN = JSONWriteOptions(rawValue: YYJSON_WRITE_ALLOW_INF_AND_NAN)
116+
117+
/// Writes infinity and NaN values as `null` literals.
118+
///
119+
/// This option takes precedence over `allowInfAndNaN`.
120+
public static let infAndNaNAsNull = JSONWriteOptions(rawValue: YYJSON_WRITE_INF_AND_NAN_AS_NULL)
121+
122+
/// Allow invalid unicode when encoding string values.
123+
public static let allowInvalidUnicode = JSONWriteOptions(rawValue: YYJSON_WRITE_ALLOW_INVALID_UNICODE)
124+
125+
/// Add a newline character at the end of the JSON.
126+
public static let newlineAtEnd = JSONWriteOptions(rawValue: YYJSON_WRITE_NEWLINE_AT_END)
127+
128+
/// Sorts object keys lexicographically.
129+
public static let sortedKeys = JSONWriteOptions(rawValue: 1 << 16)
130+
131+
// Mask for Swift-only flags (bits 16+) that should not be passed to yyjson C library
132+
private static let swiftOnlyFlagsMask: UInt32 = 0xFFFF_0000
133+
134+
/// Convert to yyjson write flags, excluding Swift-only flags.
135+
internal var yyjsonFlags: yyjson_write_flag {
136+
// Only pass bits 0-15 to yyjson C library; bits 16+ are Swift-only flags
137+
yyjson_write_flag(rawValue & ~JSONWriteOptions.swiftOnlyFlagsMask)
138+
}
139+
}

0 commit comments

Comments
 (0)