You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Many users only need to parse simple JSON messages, which you can do with the `JSON.Node.init(parsing:)` initializer:
34
+
35
+
```swift
36
+
importJSON
37
+
38
+
let string: String="""
39
+
{"success": true, "value": 0.1}
40
+
"""
41
+
42
+
let root: JSON.Node =try .init(parsing: string)
43
+
```
44
+
45
+
If you have UTF-8 data in array form, you can skip the string entirely, and bind your native `UInt8` buffer to an instance of [`JSON`](https://swiftinit.org/docs/swift-json/jsonast/json).
46
+
47
+
```swift
48
+
let json: JSON = .init(utf8: buffer[...])
49
+
let root: JSON.Node =try .init(parsing: json)
50
+
```
51
+
52
+
The difference between `JSON` and [`JSON.Node`](https://swiftinit.org/docs/swift-json/jsonast/json/node) is the former is a unparsed buffer wrapper while the latter is a fully-parsed JSON abstract syntax tree (AST). The separation allows you to strongly-type JSON data without necessarily paying the cost of parsing it up front.
53
+
54
+
`JSON` is backed by `ArraySlice<UInt8>` to help you avoid unnecessary buffer copies.
55
+
56
+
Depending on what you want to do with JSON, you will either want to use the **query API** (`JQ`, named for the iconic command line tool it was inspired by), or the **modeling API**.
57
+
58
+
59
+
### Using the query API
60
+
61
+
The **query API** is good for when you want to extract data from JSON payloads at known locations, or make surgical modifications to the JSON without having to model, or even decode, irrelevant portions of the payload. The syntax should look instantly familiar if you have ever used the `jq` command line tool.
62
+
63
+
```swift
64
+
importJSON
65
+
importJQ
66
+
67
+
let string: String="""
68
+
{"id": 1, "scores": []}
69
+
"""
70
+
71
+
var root: JSON.Node =try .init(parsing: string)
72
+
try root["name"] &="Barbara"
73
+
try root["scores"][0] &= .number(5)
74
+
75
+
// {"id":1,"scores":[5],"name":"Barbara"}
76
+
```
77
+
78
+
Assigning to JSON paths can throw an error if data already exists in that location, and the node in there is not compatible with the query path.
79
+
80
+
```swift
81
+
do {
82
+
try root["scores"]["banana"] &=true
83
+
} catchleterror {
84
+
// cannot write to protected json node 'scores'
85
+
print(error)
86
+
}
87
+
```
88
+
89
+
Alternatively, if you just want to read data without modifying it, you can do that by calling the `node` property on the accessor.
For this reason, more-sophisticated create-modify-delete operations are often better-served by the yielding accessor APIs, which take a closure argument and supply the caller with the preimage of the node being modified.
114
+
115
+
The yielding accessors are spelled with the `&`, `&?`, and `&!` operators.
116
+
117
+
```swift
118
+
/// this only creates the wrapper objects if the node is
119
+
/// assigned a non-nil value
120
+
try root["profile"]["address"]["city"] & {
121
+
ifBool.random() {
122
+
$0= .string("Tehran")
123
+
}
124
+
}
125
+
126
+
/// this only calls the closure if the node already exists
127
+
root["profile"]["address"]["city"] &? {
128
+
ifcase .string(let string) =$0 {
129
+
$0= .string(string.value.uppercased())
130
+
}
131
+
}
132
+
```
133
+
134
+
The most general form of `&` yields the accessed node as `(inout JSON.Node?)`, but it has a variant, `&!`, that passes the caller an `(inout JSON.Node)` binding. `&!` can be thought of as a special case of `&` that initializes the node with a default value of `null` if it does not already exist.
135
+
136
+
Do note that this `null` is a “hard” `null`, thus if you use `&!`, all wrapper objects will be created, and unlike the fully-general `&`, the update will fail if that `null` can’t be safely written back to the AST.
137
+
138
+
```swift
139
+
/// this initializes the node with a default value (of `null`)
140
+
/// if it does not already exist
141
+
try root["x"]["y"]["z"] &! {
142
+
ifBool.random() {
143
+
$0=true
144
+
}
145
+
}
146
+
```
147
+
148
+
Empty brackets are used to bind a node to an array type. The array version of `&!` is just like the general version of `&!`, except it initializes missing fields to empty arrays instead of `null`. There is also `&?`, if the convenience of receiving a non-optional `(inout JSON.Array)` is more important to you than the flexibility to conditionally create or delete the array.
149
+
150
+
```swift
151
+
/// this appends a value to the array only if it already exists
152
+
try root["scores"][] & {
153
+
$0?.elements.append(.number(8))
154
+
}
155
+
156
+
/// this initializes the field to an empty array if it does not already exist
157
+
try root["friends"][] &! {
158
+
$0.elements.append("Paris")
159
+
}
160
+
// {"id":1,"scores":[5,8],"friends":["Paris"]}
161
+
```
162
+
163
+
Finally, it’s worth highlighting the powerful `|` and `|?` operators, which provide a concise means of expressing map operations over array fields.
164
+
165
+
```swift
166
+
let scores: [Int]?=try root["scores"][] | { tryInt.init(json: $0) }
167
+
```
168
+
169
+
The only difference between `|` and `|?` is that the latter ignores invalid access paths and simply returns nil if the path is incompatible with the container.
170
+
171
+
There is no syntactical sugar for modifying array elements in place, to do that, you can just compose other library APIs with native Swift loops.
172
+
173
+
```swift
174
+
/// this adds 1 to each score in the 'scores' array
175
+
try root["scores"][] &! {
176
+
for i:Intin$0.indices {
177
+
let score: Int=try$0[i].decode()
178
+
$0.elements[i] = .number(score +1)
179
+
}
180
+
}
181
+
// {"id":1,"scores":[6,9],"friends":["Paris"]}
182
+
```
183
+
184
+
Most developers using the query API import `JSON` alongside `JQ`, to take advantage of the library’s built-in error handling for casting JSON types. That is where the `try $0[i].decode()` API that we used to cast each `score` to `Int` came from. But `JQ` doesn’t actually depend on the full JSON parser, encoder, or decoder. If you are aggressively stripping down dependencies, you can get away with just importing `JSONAST`, which provides the minimal set of tools for working with JSON trees.
185
+
186
+
```swift
187
+
importJSONAST
188
+
importJQ
189
+
```
190
+
191
+
### Using the modeling API
192
+
193
+
Unlike the query API, the **modeling API** is an indexed API — it builds random-access indexes of the JSON, which makes it more efficient if you are trying to destructure a large portion of the data in a payload as opposed to a small subset.
194
+
195
+
As the name suggests, using the modeling API involves defining the full schema of the JSON you expect to receive. It requires writing more code, but is also significantly more type safe (as it involves reifying the schema into Swift structures), and also enables blazing fast JSON encoding performance, as modeled types know how to serialize themselves to raw JSON buffer output without building syntax trees at all.
196
+
197
+
Getting the most out of the modeling API will require learning and internalizing a set of reusable code patterns that can be composed to build roundtripping logic for sophisticated JSON data models. We suggest [reading the tutorial](https://swiftinit.org/docs/swift-json/json/decoding) to get started.
0 commit comments