@zio.dev/zio-blocks 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.md +426 -0
- package/package.json +6 -0
- package/path-interpolator.md +645 -0
- package/reference/binding.md +364 -0
- package/reference/chunk.md +576 -0
- package/reference/context.md +157 -0
- package/reference/docs.md +524 -0
- package/reference/dynamic-value.md +823 -0
- package/reference/formats.md +640 -0
- package/reference/json-schema.md +626 -0
- package/reference/json.md +979 -0
- package/reference/modifier.md +276 -0
- package/reference/optics.md +1613 -0
- package/reference/patch.md +631 -0
- package/reference/reflect-transform.md +387 -0
- package/reference/reflect.md +521 -0
- package/reference/registers.md +282 -0
- package/reference/schema-evolution.md +540 -0
- package/reference/schema.md +619 -0
- package/reference/syntax.md +409 -0
- package/reference/type-class-derivation-internals.md +632 -0
- package/reference/typeid.md +900 -0
- package/reference/validation.md +458 -0
- package/scope.md +627 -0
- package/sidebars.js +30 -0
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: json
|
|
3
|
+
title: "Json"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Json` is an algebraic data type (ADT) for representing JSON values in ZIO Blocks. It provides a type-safe, schema-free way to work with JSON data, enabling navigation, transformation, merging, and querying without losing fidelity.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The `Json` type represents all valid JSON values with six cases:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Json
|
|
14
|
+
├── Json.Object (key-value pairs, order-preserving)
|
|
15
|
+
├── Json.Array (ordered sequence of values)
|
|
16
|
+
├── Json.String (text)
|
|
17
|
+
├── Json.Number (arbitrary precision via BigDecimal)
|
|
18
|
+
├── Json.Boolean (true/false)
|
|
19
|
+
└── Json.Null (null)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Key design decisions:
|
|
23
|
+
|
|
24
|
+
- **Objects use `Vector[(String, Json)]`** to preserve insertion order while providing order-independent equality
|
|
25
|
+
- **Numbers use `BigDecimal`** to preserve precision for financial and scientific data
|
|
26
|
+
- **All navigation returns `JsonSelection`** for fluent, composable chaining
|
|
27
|
+
|
|
28
|
+
## Creating JSON Values
|
|
29
|
+
|
|
30
|
+
### Using Constructors
|
|
31
|
+
|
|
32
|
+
```scala mdoc:compile-only
|
|
33
|
+
import zio.blocks.schema.json.Json
|
|
34
|
+
|
|
35
|
+
// Object with named fields
|
|
36
|
+
val person = Json.Object(
|
|
37
|
+
"name" -> Json.String("Alice"),
|
|
38
|
+
"age" -> Json.Number(30),
|
|
39
|
+
"active" -> Json.Boolean(true)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// Array of values
|
|
43
|
+
val numbers = Json.Array(Json.Number(1), Json.Number(2), Json.Number(3))
|
|
44
|
+
|
|
45
|
+
// Primitive values
|
|
46
|
+
val name = Json.String("Bob")
|
|
47
|
+
val count = Json.Number(42)
|
|
48
|
+
val flag = Json.Boolean(false)
|
|
49
|
+
val nothing = Json.Null
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Parsing JSON Strings
|
|
53
|
+
|
|
54
|
+
```scala mdoc:compile-only
|
|
55
|
+
import zio.blocks.schema.json.Json
|
|
56
|
+
import zio.blocks.schema.SchemaError
|
|
57
|
+
|
|
58
|
+
// Safe parsing (returns Either)
|
|
59
|
+
val parsed: Either[SchemaError, Json] = Json.parse("""{"name": "Alice", "age": 30}""")
|
|
60
|
+
|
|
61
|
+
// Unsafe parsing (throws on error)
|
|
62
|
+
val json = Json.parseUnsafe("""{"items": [1, 2, 3]}""")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### String Interpolators
|
|
66
|
+
|
|
67
|
+
ZIO Blocks provides compile-time validated string interpolators for JSON:
|
|
68
|
+
|
|
69
|
+
```scala mdoc:compile-only
|
|
70
|
+
import zio.blocks.schema._
|
|
71
|
+
import zio.blocks.schema.json._
|
|
72
|
+
|
|
73
|
+
// JSON literal with compile-time validation
|
|
74
|
+
val person = json"""{"name": "Alice", "age": 30}"""
|
|
75
|
+
|
|
76
|
+
// With Scala value interpolation
|
|
77
|
+
val name = "Bob"
|
|
78
|
+
val age = 25
|
|
79
|
+
val person2 = json"""{"name": $name, "age": $age}"""
|
|
80
|
+
|
|
81
|
+
// Path interpolator for navigation
|
|
82
|
+
val path = p".users[0].name"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The `json"..."` interpolator validates JSON syntax at compile time, catching errors before runtime.
|
|
86
|
+
|
|
87
|
+
## Type Testing and Access
|
|
88
|
+
|
|
89
|
+
### Unified Type Operations
|
|
90
|
+
|
|
91
|
+
The `Json` type provides unified methods for type testing and narrowing with path-dependent return types.
|
|
92
|
+
`JsonType` also implements `Json => Boolean`, so it can be used directly as a predicate for filtering.
|
|
93
|
+
|
|
94
|
+
```scala mdoc:compile-only
|
|
95
|
+
import zio.blocks.schema.json.{Json, JsonType}
|
|
96
|
+
|
|
97
|
+
val json: Json = Json.parseUnsafe("""{"count": 42}""")
|
|
98
|
+
|
|
99
|
+
// Type testing with is()
|
|
100
|
+
json.is(JsonType.Object) // true
|
|
101
|
+
json.is(JsonType.Array) // false
|
|
102
|
+
|
|
103
|
+
// Type narrowing with as() - returns Option[jsonType.Type]
|
|
104
|
+
val obj: Option[Json.Object] = json.as(JsonType.Object) // Some(Json.Object(...))
|
|
105
|
+
val arr: Option[Json.Array] = json.as(JsonType.Array) // None
|
|
106
|
+
|
|
107
|
+
// Value extraction with unwrap() - returns Option[jsonType.Unwrap]
|
|
108
|
+
val str: Json = Json.String("hello")
|
|
109
|
+
val strValue: Option[String] = str.unwrap(JsonType.String) // Some("hello")
|
|
110
|
+
|
|
111
|
+
val num: Json = Json.Number(42)
|
|
112
|
+
val numValue: Option[BigDecimal] = num.unwrap(JsonType.Number) // Some(42)
|
|
113
|
+
|
|
114
|
+
// JsonType as predicate - use directly in selection query
|
|
115
|
+
val strings = json.select.query(JsonType.String) // all string values in the JSON tree
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Direct Value Access
|
|
119
|
+
|
|
120
|
+
```scala mdoc:compile-only
|
|
121
|
+
import zio.blocks.schema.json.Json
|
|
122
|
+
|
|
123
|
+
val obj = Json.Object("a" -> Json.Number(1))
|
|
124
|
+
obj.fields // Chunk(("a", Json.Number(1)))
|
|
125
|
+
|
|
126
|
+
val arr = Json.Array(Json.Number(1), Json.Number(2))
|
|
127
|
+
arr.elements // Chunk(Json.Number(1), Json.Number(2))
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Navigation
|
|
131
|
+
|
|
132
|
+
### Simple Navigation
|
|
133
|
+
|
|
134
|
+
Navigate into objects by key and arrays by index:
|
|
135
|
+
|
|
136
|
+
```scala mdoc:compile-only
|
|
137
|
+
import zio.blocks.schema.json.Json
|
|
138
|
+
import zio.blocks.schema.SchemaError
|
|
139
|
+
|
|
140
|
+
val json = Json.parseUnsafe("""{
|
|
141
|
+
"users": [
|
|
142
|
+
{"name": "Alice", "age": 30},
|
|
143
|
+
{"name": "Bob", "age": 25}
|
|
144
|
+
]
|
|
145
|
+
}""")
|
|
146
|
+
|
|
147
|
+
// Navigate to a field
|
|
148
|
+
val users = json.get("users") // JsonSelection
|
|
149
|
+
|
|
150
|
+
// Navigate to an array element
|
|
151
|
+
val firstUser = json.get("users")(0) // JsonSelection
|
|
152
|
+
|
|
153
|
+
// Chain navigation
|
|
154
|
+
val firstName = json.get("users")(0).get("name") // JsonSelection
|
|
155
|
+
|
|
156
|
+
// Extract the value
|
|
157
|
+
val name: Either[SchemaError, String] = firstName.as[String] // Right("Alice")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Path-Based Navigation with DynamicOptic
|
|
161
|
+
|
|
162
|
+
Use `DynamicOptic` paths for complex navigation:
|
|
163
|
+
|
|
164
|
+
```scala mdoc:compile-only
|
|
165
|
+
import zio.blocks.schema._
|
|
166
|
+
import zio.blocks.schema.json._
|
|
167
|
+
|
|
168
|
+
val json = Json.parseUnsafe("""{
|
|
169
|
+
"company": {
|
|
170
|
+
"employees": [
|
|
171
|
+
{"name": "Alice", "department": "Engineering"},
|
|
172
|
+
{"name": "Bob", "department": "Sales"}
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
}""")
|
|
176
|
+
|
|
177
|
+
// Using path interpolator
|
|
178
|
+
val path = p".company.employees[0].name"
|
|
179
|
+
val name = json.get(path).as[String] // Right("Alice")
|
|
180
|
+
|
|
181
|
+
// Equivalent to chained navigation
|
|
182
|
+
val sameName = json.get("company").get("employees")(0).get("name").as[String]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## JsonSelection
|
|
186
|
+
|
|
187
|
+
`JsonSelection` is a fluent wrapper for navigation results, enabling composable chaining:
|
|
188
|
+
|
|
189
|
+
```scala mdoc:compile-only
|
|
190
|
+
import zio.blocks.schema.json.{Json, JsonSelection}
|
|
191
|
+
|
|
192
|
+
val json = Json.parseUnsafe("""{"users": [{"name": "Alice"}]}""")
|
|
193
|
+
|
|
194
|
+
// Fluent chaining
|
|
195
|
+
val result: JsonSelection = json
|
|
196
|
+
.get("users")
|
|
197
|
+
.arrays
|
|
198
|
+
.apply(0)
|
|
199
|
+
.get("name")
|
|
200
|
+
.strings
|
|
201
|
+
|
|
202
|
+
// Extract values
|
|
203
|
+
result.as[String] // Right("Alice")
|
|
204
|
+
result.one // Right(Json.String("Alice"))
|
|
205
|
+
result.isSuccess // true
|
|
206
|
+
result.isFailure // false
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Terminal Operations
|
|
210
|
+
|
|
211
|
+
```scala mdoc:compile-only
|
|
212
|
+
import zio.blocks.schema.json.{Json, JsonSelection}
|
|
213
|
+
import zio.blocks.schema.SchemaError
|
|
214
|
+
|
|
215
|
+
val selection: JsonSelection = ???
|
|
216
|
+
|
|
217
|
+
// Get single value (exactly one required)
|
|
218
|
+
val oneValue: Either[SchemaError, Json] = selection.one
|
|
219
|
+
// Get any single value (first of many)
|
|
220
|
+
val anyValue: Either[SchemaError, Json] = selection.any
|
|
221
|
+
// Get all values condensed (wraps multiple in array)
|
|
222
|
+
val allValues: Either[SchemaError, Json] = selection.all
|
|
223
|
+
|
|
224
|
+
// Get underlying result
|
|
225
|
+
val underlying: Either[SchemaError, Vector[Json]] = selection.either
|
|
226
|
+
val asVector: Vector[Json] = selection.toVector // empty on error
|
|
227
|
+
|
|
228
|
+
// Decode to specific types
|
|
229
|
+
val asString: Either[SchemaError, String] = selection.as[String]
|
|
230
|
+
val asBigDecimal: Either[SchemaError, BigDecimal] = selection.as[BigDecimal]
|
|
231
|
+
val asBoolean: Either[SchemaError, Boolean] = selection.as[Boolean]
|
|
232
|
+
val asInt: Either[SchemaError, Int] = selection.as[Int]
|
|
233
|
+
val asLong: Either[SchemaError, Long] = selection.as[Long]
|
|
234
|
+
val asDouble: Either[SchemaError, Double] = selection.as[Double]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Modification
|
|
238
|
+
|
|
239
|
+
### Setting Values
|
|
240
|
+
|
|
241
|
+
```scala mdoc:compile-only
|
|
242
|
+
import zio.blocks.schema._
|
|
243
|
+
import zio.blocks.schema.json._
|
|
244
|
+
|
|
245
|
+
val json = Json.parseUnsafe("""{"user": {"name": "Alice", "age": 30}}""")
|
|
246
|
+
|
|
247
|
+
// Set a value at a path
|
|
248
|
+
val updated = json.set(p".user.name", Json.String("Bob"))
|
|
249
|
+
// {"user": {"name": "Bob", "age": 30}}
|
|
250
|
+
|
|
251
|
+
// Set with failure handling
|
|
252
|
+
val result = json.setOrFail(p".user.email", Json.String("alice@example.com"))
|
|
253
|
+
// Left(SchemaError) - path doesn't exist
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Modifying Values
|
|
257
|
+
|
|
258
|
+
```scala mdoc:compile-only
|
|
259
|
+
import zio.blocks.schema._
|
|
260
|
+
import zio.blocks.schema.json._
|
|
261
|
+
|
|
262
|
+
val json = Json.parseUnsafe("""{"count": 10}""")
|
|
263
|
+
|
|
264
|
+
// Modify with a function
|
|
265
|
+
val incremented = json.modify(p".count") {
|
|
266
|
+
case Json.Number(n) => Json.Number(n + 1)
|
|
267
|
+
case other => other
|
|
268
|
+
}
|
|
269
|
+
// {"count": 11}
|
|
270
|
+
|
|
271
|
+
// Modify with failure on missing path
|
|
272
|
+
val result = json.modifyOrFail(p".count") {
|
|
273
|
+
case Json.Number(n) => Json.Number(n * 2)
|
|
274
|
+
}
|
|
275
|
+
// Right({"count": 20})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Deleting Values
|
|
279
|
+
|
|
280
|
+
```scala mdoc:compile-only
|
|
281
|
+
import zio.blocks.schema._
|
|
282
|
+
import zio.blocks.schema.json._
|
|
283
|
+
|
|
284
|
+
val json = Json.parseUnsafe("""{"a": 1, "b": 2, "c": 3}""")
|
|
285
|
+
|
|
286
|
+
// Delete a field
|
|
287
|
+
val withoutB = json.delete(p".b")
|
|
288
|
+
// {"a": 1, "c": 3}
|
|
289
|
+
|
|
290
|
+
// Delete with failure handling
|
|
291
|
+
val result = json.deleteOrFail(p".missing")
|
|
292
|
+
// Left(SchemaError) - path doesn't exist
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Inserting Values
|
|
296
|
+
|
|
297
|
+
```scala mdoc:compile-only
|
|
298
|
+
import zio.blocks.schema._
|
|
299
|
+
import zio.blocks.schema.json._
|
|
300
|
+
|
|
301
|
+
val json = Json.parseUnsafe("""{"existing": 1}""")
|
|
302
|
+
|
|
303
|
+
// Insert a new field
|
|
304
|
+
val withNew = json.insert(p".newField", Json.String("value"))
|
|
305
|
+
// {"existing": 1, "newField": "value"}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Transformation
|
|
309
|
+
|
|
310
|
+
### Transform Up (Bottom-Up)
|
|
311
|
+
|
|
312
|
+
Transform children before parents:
|
|
313
|
+
|
|
314
|
+
```scala mdoc:compile-only
|
|
315
|
+
import zio.blocks.schema.json.Json
|
|
316
|
+
import zio.blocks.schema.DynamicOptic
|
|
317
|
+
|
|
318
|
+
val json = Json.parseUnsafe("""{"values": [1, 2, 3]}""")
|
|
319
|
+
|
|
320
|
+
// Double all numbers
|
|
321
|
+
val doubled = json.transformUp { (path, value) =>
|
|
322
|
+
value match {
|
|
323
|
+
case Json.Number(n) => Json.Number(n * 2)
|
|
324
|
+
case other => other
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// {"values": [2, 4, 6]}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Transform Down (Top-Down)
|
|
331
|
+
|
|
332
|
+
Transform parents before children:
|
|
333
|
+
|
|
334
|
+
```scala mdoc:compile-only
|
|
335
|
+
import zio.blocks.schema.json.Json
|
|
336
|
+
import zio.blocks.schema.DynamicOptic
|
|
337
|
+
|
|
338
|
+
val json = Json.parseUnsafe("""{"items": [{"x": 1}, {"x": 2}]}""")
|
|
339
|
+
|
|
340
|
+
// Add a field to all objects
|
|
341
|
+
val withId = json.transformDown { (path, value) =>
|
|
342
|
+
value match {
|
|
343
|
+
case Json.Object(fields) if !fields.exists(_._1 == "id") =>
|
|
344
|
+
new Json.Object(("id" -> Json.String(path.toString)) +: fields)
|
|
345
|
+
case other => other
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Transform Keys
|
|
351
|
+
|
|
352
|
+
Rename object keys throughout the structure:
|
|
353
|
+
|
|
354
|
+
```scala mdoc:compile-only
|
|
355
|
+
import zio.blocks.schema.json.Json
|
|
356
|
+
|
|
357
|
+
val json = Json.parseUnsafe("""{"user_name": "Alice", "user_age": 30}""")
|
|
358
|
+
|
|
359
|
+
// Convert snake_case to camelCase
|
|
360
|
+
val camelCase = json.transformKeys { (path, key) =>
|
|
361
|
+
key.split("_").zipWithIndex.map {
|
|
362
|
+
case (word, 0) => word
|
|
363
|
+
case (word, _) => word.capitalize
|
|
364
|
+
}.mkString
|
|
365
|
+
}
|
|
366
|
+
// {"userName": "Alice", "userAge": 30}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Filtering
|
|
370
|
+
|
|
371
|
+
### Filter Values
|
|
372
|
+
|
|
373
|
+
Keep only values matching a predicate using `retain`, or remove values using `prune`:
|
|
374
|
+
|
|
375
|
+
```scala mdoc:compile-only
|
|
376
|
+
import zio.blocks.schema.json.{Json, JsonType}
|
|
377
|
+
|
|
378
|
+
val json = Json.parseUnsafe("""{"a": 1, "b": null, "c": 2, "d": null}""")
|
|
379
|
+
|
|
380
|
+
// Remove nulls using prune (removes values matching predicate)
|
|
381
|
+
val noNulls = json.prune(_.is(JsonType.Null))
|
|
382
|
+
// {"a": 1, "c": 2}
|
|
383
|
+
|
|
384
|
+
// Keep only numbers using retain (keeps values matching predicate)
|
|
385
|
+
val onlyNumbers = json.retain(_.is(JsonType.Number))
|
|
386
|
+
// {"a": 1, "c": 2}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Project Paths
|
|
390
|
+
|
|
391
|
+
Extract only specific paths:
|
|
392
|
+
|
|
393
|
+
```scala mdoc:compile-only
|
|
394
|
+
import zio.blocks.schema._
|
|
395
|
+
import zio.blocks.schema.json._
|
|
396
|
+
|
|
397
|
+
val json = Json.parseUnsafe("""{
|
|
398
|
+
"user": {"name": "Alice", "email": "alice@example.com", "password": "secret"},
|
|
399
|
+
"metadata": {"created": "2024-01-01"}
|
|
400
|
+
}""")
|
|
401
|
+
|
|
402
|
+
// Keep only specific fields
|
|
403
|
+
val projected = json.project(p".user.name", p".user.email")
|
|
404
|
+
// {"user": {"name": "Alice", "email": "alice@example.com"}}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Partition
|
|
408
|
+
|
|
409
|
+
Split based on a predicate:
|
|
410
|
+
|
|
411
|
+
```scala mdoc:compile-only
|
|
412
|
+
import zio.blocks.schema.json.{Json, JsonType}
|
|
413
|
+
|
|
414
|
+
val json = Json.parseUnsafe("""{"a": 1, "b": "text", "c": 2}""")
|
|
415
|
+
|
|
416
|
+
// Separate numbers from non-numbers
|
|
417
|
+
val (numbers, nonNumbers) = json.partition(_.is(JsonType.Number))
|
|
418
|
+
// numbers: {"a": 1, "c": 2}
|
|
419
|
+
// nonNumbers: {"b": "text"}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Folding
|
|
423
|
+
|
|
424
|
+
### Fold Up (Bottom-Up)
|
|
425
|
+
|
|
426
|
+
Accumulate values from children to parents:
|
|
427
|
+
|
|
428
|
+
```scala mdoc:compile-only
|
|
429
|
+
import zio.blocks.schema.json.Json
|
|
430
|
+
|
|
431
|
+
val json = Json.parseUnsafe("""{"values": [1, 2, 3, 4, 5]}""")
|
|
432
|
+
|
|
433
|
+
// Sum all numbers
|
|
434
|
+
val sum = json.foldUp(BigDecimal(0)) { (path, value, acc) =>
|
|
435
|
+
value match {
|
|
436
|
+
case n: Json.Number => acc + n.toBigDecimal
|
|
437
|
+
case _ => acc
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// sum = 15
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Fold Down (Top-Down)
|
|
444
|
+
|
|
445
|
+
Accumulate values from parents to children:
|
|
446
|
+
|
|
447
|
+
```scala mdoc:compile-only
|
|
448
|
+
import zio.blocks.schema.json.Json
|
|
449
|
+
import zio.blocks.schema.DynamicOptic
|
|
450
|
+
|
|
451
|
+
val json = Json.parseUnsafe("""{"a": {"b": {"c": 1}}}""")
|
|
452
|
+
|
|
453
|
+
// Collect all paths
|
|
454
|
+
val paths = json.foldDown(Vector.empty[DynamicOptic]) { (path, value, acc) =>
|
|
455
|
+
acc :+ path
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Merging
|
|
460
|
+
|
|
461
|
+
Combine two JSON values using different strategies:
|
|
462
|
+
|
|
463
|
+
```scala mdoc:compile-only
|
|
464
|
+
import zio.blocks.schema.json.{Json, MergeStrategy}
|
|
465
|
+
|
|
466
|
+
val base = Json.parseUnsafe("""{"a": 1, "b": {"x": 10}}""")
|
|
467
|
+
val overlay = Json.parseUnsafe("""{"b": {"y": 20}, "c": 3}""")
|
|
468
|
+
|
|
469
|
+
// Auto strategy (default) - deep merge objects, concat arrays
|
|
470
|
+
val merged = base.merge(overlay)
|
|
471
|
+
// {"a": 1, "b": {"x": 10, "y": 20}, "c": 3}
|
|
472
|
+
|
|
473
|
+
// Shallow merge (only top-level)
|
|
474
|
+
val shallow = base.merge(overlay, MergeStrategy.Shallow)
|
|
475
|
+
|
|
476
|
+
// Replace (right wins)
|
|
477
|
+
val replaced = base.merge(overlay, MergeStrategy.Replace)
|
|
478
|
+
// {"b": {"y": 20}, "c": 3}
|
|
479
|
+
|
|
480
|
+
// Concat arrays
|
|
481
|
+
val concat = base.merge(overlay, MergeStrategy.Concat)
|
|
482
|
+
|
|
483
|
+
// Custom strategy
|
|
484
|
+
val custom = base.merge(overlay, MergeStrategy.Custom { (path, left, right) =>
|
|
485
|
+
// Your merge logic here
|
|
486
|
+
right
|
|
487
|
+
})
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Merge Strategies
|
|
491
|
+
|
|
492
|
+
| Strategy | Objects | Arrays | Primitives |
|
|
493
|
+
|----------|---------|--------|------------|
|
|
494
|
+
| `Auto` | Deep merge | Concatenate | Replace |
|
|
495
|
+
| `Deep` | Recursive merge | Concatenate | Replace |
|
|
496
|
+
| `Shallow` | Top-level only | Concatenate | Replace |
|
|
497
|
+
| `Replace` | Right wins | Right wins | Right wins |
|
|
498
|
+
| `Concat` | Merge keys | Concatenate | Replace |
|
|
499
|
+
| `Custom(f)` | User-defined | User-defined | User-defined |
|
|
500
|
+
|
|
501
|
+
## Normalization
|
|
502
|
+
|
|
503
|
+
Clean up JSON values:
|
|
504
|
+
|
|
505
|
+
```scala mdoc:compile-only
|
|
506
|
+
import zio.blocks.schema.json.Json
|
|
507
|
+
|
|
508
|
+
val json = Json.parseUnsafe("""{
|
|
509
|
+
"z": 1,
|
|
510
|
+
"a": null,
|
|
511
|
+
"m": {"empty": {}},
|
|
512
|
+
"b": 2
|
|
513
|
+
}""")
|
|
514
|
+
|
|
515
|
+
// Sort object keys alphabetically
|
|
516
|
+
val sorted = json.sortKeys
|
|
517
|
+
// {"a": null, "b": 2, "m": {"empty": {}}, "z": 1}
|
|
518
|
+
|
|
519
|
+
// Remove null values
|
|
520
|
+
val noNulls = json.dropNulls
|
|
521
|
+
// {"z": 1, "m": {"empty": {}}, "b": 2}
|
|
522
|
+
|
|
523
|
+
// Remove empty objects and arrays
|
|
524
|
+
val noEmpty = json.dropEmpty
|
|
525
|
+
// {"z": 1, "a": null, "b": 2}
|
|
526
|
+
|
|
527
|
+
// Apply all normalizations
|
|
528
|
+
val normalized = json.normalize
|
|
529
|
+
// {"b": 2, "z": 1}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
## Encoding and Decoding
|
|
533
|
+
|
|
534
|
+
### Type Classes
|
|
535
|
+
|
|
536
|
+
ZIO Blocks provides `JsonEncoder` and `JsonDecoder` type classes for converting between Scala types and `Json`:
|
|
537
|
+
|
|
538
|
+
```scala mdoc:compile-only
|
|
539
|
+
import zio.blocks.schema.json.{Json, JsonEncoder, JsonDecoder}
|
|
540
|
+
|
|
541
|
+
// Encode Scala values to Json
|
|
542
|
+
val intJson = JsonEncoder[Int].encode(42) // Json.Number(42)
|
|
543
|
+
val strJson = JsonEncoder[String].encode("hello") // Json.String("hello")
|
|
544
|
+
|
|
545
|
+
// Decode Json to Scala values
|
|
546
|
+
val intResult = JsonDecoder[Int].decode(Json.Number(42)) // Right(42)
|
|
547
|
+
val strResult = JsonDecoder[String].decode(Json.String("hello")) // Right("hello")
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Built-in Encoders/Decoders
|
|
551
|
+
|
|
552
|
+
```scala mdoc:compile-only
|
|
553
|
+
import zio.blocks.schema.json.{JsonEncoder, JsonDecoder}
|
|
554
|
+
|
|
555
|
+
// Primitives
|
|
556
|
+
JsonEncoder[String]
|
|
557
|
+
JsonEncoder[Int]
|
|
558
|
+
JsonEncoder[Long]
|
|
559
|
+
JsonEncoder[Double]
|
|
560
|
+
JsonEncoder[Boolean]
|
|
561
|
+
JsonEncoder[BigDecimal]
|
|
562
|
+
|
|
563
|
+
// Collections
|
|
564
|
+
JsonEncoder[List[Int]]
|
|
565
|
+
JsonEncoder[Vector[String]]
|
|
566
|
+
JsonEncoder[Map[String, Int]]
|
|
567
|
+
JsonEncoder[Option[String]]
|
|
568
|
+
|
|
569
|
+
// Java time types
|
|
570
|
+
JsonEncoder[java.time.Instant]
|
|
571
|
+
JsonEncoder[java.time.LocalDate]
|
|
572
|
+
JsonEncoder[java.time.ZonedDateTime]
|
|
573
|
+
JsonEncoder[java.util.UUID]
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Schema-Based Derivation
|
|
577
|
+
|
|
578
|
+
For complex types, use Schema-based derivation:
|
|
579
|
+
|
|
580
|
+
```scala mdoc:compile-only
|
|
581
|
+
import zio.blocks.schema.Schema
|
|
582
|
+
import zio.blocks.schema.json.{Json, JsonEncoder, JsonDecoder}
|
|
583
|
+
|
|
584
|
+
case class Person(name: String, age: Int)
|
|
585
|
+
|
|
586
|
+
object Person {
|
|
587
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
588
|
+
|
|
589
|
+
// Derived from schema (lower priority)
|
|
590
|
+
implicit val encoder: JsonEncoder[Person] = JsonEncoder.fromSchema
|
|
591
|
+
implicit val decoder: JsonDecoder[Person] = JsonDecoder.fromSchema
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
val person = Person("Alice", 30)
|
|
595
|
+
val json = JsonEncoder[Person].encode(person)
|
|
596
|
+
val decoded = JsonDecoder[Person].decode(json)
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Extension Syntax
|
|
600
|
+
|
|
601
|
+
When a `Schema` is in scope, you can use convenient extension methods directly on values:
|
|
602
|
+
|
|
603
|
+
```scala mdoc:compile-only
|
|
604
|
+
import zio.blocks.schema._
|
|
605
|
+
|
|
606
|
+
case class Person(name: String, age: Int)
|
|
607
|
+
object Person {
|
|
608
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
val person = Person("Alice", 30)
|
|
612
|
+
|
|
613
|
+
// Convert to Json AST
|
|
614
|
+
val json = person.toJson // Json.Object(...)
|
|
615
|
+
|
|
616
|
+
// Convert directly to JSON string
|
|
617
|
+
val jsonString = person.toJsonString // {"name":"Alice","age":30}
|
|
618
|
+
|
|
619
|
+
// Convert to UTF-8 bytes
|
|
620
|
+
val jsonBytes = person.toJsonBytes // Array[Byte]
|
|
621
|
+
|
|
622
|
+
// Parse JSON string back to a typed value
|
|
623
|
+
val parsed = """{"name":"Bob","age":25}""".fromJson[Person] // Right(Person("Bob", 25))
|
|
624
|
+
|
|
625
|
+
// Parse from bytes
|
|
626
|
+
val fromBytes = jsonBytes.fromJson[Person] // Right(Person("Alice", 30))
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
These extension methods provide a more ergonomic API compared to explicitly creating encoders/decoders.
|
|
630
|
+
|
|
631
|
+
### Using the `as` Method
|
|
632
|
+
|
|
633
|
+
```scala mdoc:compile-only
|
|
634
|
+
import zio.blocks.schema.json.Json
|
|
635
|
+
import zio.blocks.schema.json.JsonDecoder
|
|
636
|
+
import zio.blocks.schema.{Schema, SchemaError}
|
|
637
|
+
|
|
638
|
+
case class Person(name: String, age: Int)
|
|
639
|
+
object Person {
|
|
640
|
+
implicit val schema: Schema[Person] = Schema.derived
|
|
641
|
+
implicit val decoder: JsonDecoder[Person] = JsonDecoder.fromSchema
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
val json = Json.parseUnsafe("""{"name": "Alice", "age": 30}""")
|
|
645
|
+
|
|
646
|
+
// Decode to a specific type
|
|
647
|
+
val person: Either[SchemaError, Person] = json.as[Person]
|
|
648
|
+
|
|
649
|
+
// Unsafe version (throws on error)
|
|
650
|
+
val personUnsafe: Person = json.asUnsafe[Person]
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## Printing JSON
|
|
654
|
+
|
|
655
|
+
### Basic Printing
|
|
656
|
+
|
|
657
|
+
```scala mdoc:compile-only
|
|
658
|
+
import zio.blocks.schema.json.Json
|
|
659
|
+
|
|
660
|
+
val json = Json.Object("name" -> Json.String("Alice"), "age" -> Json.Number(30))
|
|
661
|
+
|
|
662
|
+
// Compact output
|
|
663
|
+
val compact: String = json.print
|
|
664
|
+
// {"name":"Alice","age":30}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### With Writer Config
|
|
668
|
+
|
|
669
|
+
```scala mdoc:compile-only
|
|
670
|
+
import zio.blocks.schema.json.Json
|
|
671
|
+
import zio.blocks.schema.json.WriterConfig
|
|
672
|
+
|
|
673
|
+
val json = Json.Object("name" -> Json.String("Alice"))
|
|
674
|
+
|
|
675
|
+
// Pretty-printed output (2-space indentation)
|
|
676
|
+
val pretty = json.print(WriterConfig.withIndentionStep2)
|
|
677
|
+
// {
|
|
678
|
+
// "name": "Alice"
|
|
679
|
+
// }
|
|
680
|
+
|
|
681
|
+
// Custom indentation
|
|
682
|
+
val indented4 = json.print(WriterConfig.withIndentionStep(4))
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### WriterConfig Options
|
|
686
|
+
|
|
687
|
+
`WriterConfig` controls JSON output formatting:
|
|
688
|
+
|
|
689
|
+
| Option | Default | Description |
|
|
690
|
+
|--------|---------|-------------|
|
|
691
|
+
| `indentionStep` | `0` | Spaces per indentation level (0 = compact) |
|
|
692
|
+
| `escapeUnicode` | `false` | Escape non-ASCII characters as `\uXXXX` |
|
|
693
|
+
| `preferredBufSize` | `32768` | Internal buffer size in bytes |
|
|
694
|
+
|
|
695
|
+
```scala mdoc:compile-only
|
|
696
|
+
import zio.blocks.schema.json.WriterConfig
|
|
697
|
+
|
|
698
|
+
// Compact output (default)
|
|
699
|
+
val compact = WriterConfig
|
|
700
|
+
|
|
701
|
+
// Pretty-printed with 2-space indentation
|
|
702
|
+
val pretty = WriterConfig.withIndentionStep(2)
|
|
703
|
+
|
|
704
|
+
// Escape Unicode for ASCII-only output
|
|
705
|
+
val ascii = WriterConfig.withEscapeUnicode(true)
|
|
706
|
+
|
|
707
|
+
// Combine options
|
|
708
|
+
val custom = WriterConfig
|
|
709
|
+
.withIndentionStep(2)
|
|
710
|
+
.withEscapeUnicode(true)
|
|
711
|
+
.withPreferredBufSize(65536)
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### ReaderConfig Options
|
|
715
|
+
|
|
716
|
+
`ReaderConfig` controls JSON parsing behavior:
|
|
717
|
+
|
|
718
|
+
| Option | Default | Description |
|
|
719
|
+
|--------|---------|-------------|
|
|
720
|
+
| `preferredBufSize` | `32768` | Preferred byte buffer size |
|
|
721
|
+
| `preferredCharBufSize` | `4096` | Preferred char buffer size for strings |
|
|
722
|
+
| `maxBufSize` | `33554432` | Maximum byte buffer size (32MB) |
|
|
723
|
+
| `maxCharBufSize` | `4194304` | Maximum char buffer size (4MB) |
|
|
724
|
+
| `checkForEndOfInput` | `true` | Error on trailing non-whitespace |
|
|
725
|
+
|
|
726
|
+
```scala mdoc:compile-only
|
|
727
|
+
import zio.blocks.schema.json.ReaderConfig
|
|
728
|
+
|
|
729
|
+
// Default configuration
|
|
730
|
+
val default = ReaderConfig
|
|
731
|
+
|
|
732
|
+
// Allow trailing content (useful for streaming)
|
|
733
|
+
val lenient = ReaderConfig.withCheckForEndOfInput(false)
|
|
734
|
+
|
|
735
|
+
// Increase buffer sizes for large documents
|
|
736
|
+
val largeDoc = ReaderConfig
|
|
737
|
+
.withPreferredBufSize(65536)
|
|
738
|
+
.withPreferredCharBufSize(8192)
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### To Bytes
|
|
742
|
+
|
|
743
|
+
```scala mdoc:compile-only
|
|
744
|
+
import zio.blocks.schema.json.Json
|
|
745
|
+
|
|
746
|
+
val json = Json.Object("x" -> Json.Number(1))
|
|
747
|
+
|
|
748
|
+
// As byte array
|
|
749
|
+
val bytes: Array[Byte] = json.printBytes
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
## Query Operations
|
|
753
|
+
|
|
754
|
+
### Query with Predicate
|
|
755
|
+
|
|
756
|
+
Find all values matching a condition:
|
|
757
|
+
|
|
758
|
+
```scala mdoc:compile-only
|
|
759
|
+
import zio.blocks.schema.json.Json
|
|
760
|
+
|
|
761
|
+
val json = Json.parseUnsafe("""{
|
|
762
|
+
"users": [
|
|
763
|
+
{"name": "Alice", "active": true},
|
|
764
|
+
{"name": "Bob", "active": false},
|
|
765
|
+
{"name": "Charlie", "active": true}
|
|
766
|
+
]
|
|
767
|
+
}""")
|
|
768
|
+
|
|
769
|
+
// Find all active users using queryBoth on a selection
|
|
770
|
+
val activeUsers = json.select.queryBoth { (path, value) =>
|
|
771
|
+
value.get("active").as[Boolean].getOrElse(false)
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### Convert to Key-Value Pairs
|
|
776
|
+
|
|
777
|
+
Flatten to path-value pairs:
|
|
778
|
+
|
|
779
|
+
```scala mdoc:compile-only
|
|
780
|
+
import zio.blocks.schema.json.Json
|
|
781
|
+
import zio.blocks.schema.DynamicOptic
|
|
782
|
+
import zio.blocks.chunk.Chunk
|
|
783
|
+
|
|
784
|
+
val json = Json.parseUnsafe("""{"a": {"b": 1, "c": 2}}""")
|
|
785
|
+
|
|
786
|
+
val pairs: Chunk[(DynamicOptic, Json)] = json.toKV
|
|
787
|
+
// Chunk(
|
|
788
|
+
// ($.a.b, Json.Number(1)),
|
|
789
|
+
// ($.a.c, Json.Number(2))
|
|
790
|
+
// )
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
## Comparison and Equality
|
|
794
|
+
|
|
795
|
+
### Object Equality
|
|
796
|
+
|
|
797
|
+
Objects are compared **order-independently** (keys are compared as sorted sets):
|
|
798
|
+
|
|
799
|
+
```scala mdoc:compile-only
|
|
800
|
+
import zio.blocks.schema.json.Json
|
|
801
|
+
|
|
802
|
+
val obj1 = Json.parseUnsafe("""{"a": 1, "b": 2}""")
|
|
803
|
+
val obj2 = Json.parseUnsafe("""{"b": 2, "a": 1}""")
|
|
804
|
+
|
|
805
|
+
obj1 == obj2 // true (order-independent)
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### Ordering
|
|
809
|
+
|
|
810
|
+
JSON values have a total ordering for sorting:
|
|
811
|
+
|
|
812
|
+
```scala mdoc:compile-only
|
|
813
|
+
import zio.blocks.schema.json.Json
|
|
814
|
+
|
|
815
|
+
val values = List(
|
|
816
|
+
Json.String("z"),
|
|
817
|
+
Json.Number(1),
|
|
818
|
+
Json.Null,
|
|
819
|
+
Json.Boolean(true)
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
// Sort by type, then by value
|
|
823
|
+
val sorted = values.sortWith((a, b) => a.compare(b) < 0)
|
|
824
|
+
// [null, true, 1, "z"]
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Type ordering: Null < Boolean < Number < String < Array < Object
|
|
828
|
+
|
|
829
|
+
## JSON Diffing
|
|
830
|
+
|
|
831
|
+
`JsonDiffer` computes the difference between two JSON values, producing a `JsonPatch` that transforms the source into the target:
|
|
832
|
+
|
|
833
|
+
```scala mdoc:compile-only
|
|
834
|
+
import zio.blocks.schema.json.{Json, JsonPatch}
|
|
835
|
+
|
|
836
|
+
val source = Json.parseUnsafe("""{"name": "Alice", "age": 30}""")
|
|
837
|
+
val target = Json.parseUnsafe("""{"name": "Alice", "age": 31, "active": true}""")
|
|
838
|
+
|
|
839
|
+
// Compute the diff
|
|
840
|
+
val patch: JsonPatch = JsonPatch.diff(source, target)
|
|
841
|
+
|
|
842
|
+
// The patch describes the minimal changes:
|
|
843
|
+
// - NumberDelta for age: 30 -> 31
|
|
844
|
+
// - Add field "active": true
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
The differ uses optimal operations:
|
|
848
|
+
- **NumberDelta** for numeric changes (stores the delta, not the new value)
|
|
849
|
+
- **StringEdit** for string changes when edits are more compact than replacement
|
|
850
|
+
- **ArrayEdit** with LCS-based Insert/Delete operations for arrays
|
|
851
|
+
- **ObjectEdit** with Add/Remove/Modify operations for objects
|
|
852
|
+
|
|
853
|
+
## JSON Patching
|
|
854
|
+
|
|
855
|
+
`JsonPatch` represents a sequence of operations that transform a JSON value. Patches are composable and can be applied with different failure modes:
|
|
856
|
+
|
|
857
|
+
### Computing and Applying Patches
|
|
858
|
+
|
|
859
|
+
```scala mdoc:compile-only
|
|
860
|
+
import zio.blocks.schema.json.{Json, JsonPatch}
|
|
861
|
+
import zio.blocks.schema.patch.PatchMode
|
|
862
|
+
import zio.blocks.schema.SchemaError
|
|
863
|
+
|
|
864
|
+
val original = Json.parseUnsafe("""{"count": 10, "items": ["a", "b"]}""")
|
|
865
|
+
val modified = Json.parseUnsafe("""{"count": 15, "items": ["a", "b", "c"]}""")
|
|
866
|
+
|
|
867
|
+
// Compute the patch
|
|
868
|
+
val patch = JsonPatch.diff(original, modified)
|
|
869
|
+
|
|
870
|
+
// Apply with default (Strict) mode - fails on any precondition violation
|
|
871
|
+
val result1: Either[SchemaError, Json] = patch(original)
|
|
872
|
+
|
|
873
|
+
// Apply with Lenient mode - skips failing operations
|
|
874
|
+
val result2 = patch(original, PatchMode.Lenient)
|
|
875
|
+
|
|
876
|
+
// Apply with Clobber mode - forces changes on conflicts
|
|
877
|
+
val result3 = patch(original, PatchMode.Clobber)
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### Patch Modes
|
|
881
|
+
|
|
882
|
+
| Mode | Behavior |
|
|
883
|
+
|------|----------|
|
|
884
|
+
| `Strict` | Fail immediately on any precondition violation |
|
|
885
|
+
| `Lenient` | Skip operations that fail preconditions |
|
|
886
|
+
| `Clobber` | Force changes, overwriting on conflicts |
|
|
887
|
+
|
|
888
|
+
### Composing Patches
|
|
889
|
+
|
|
890
|
+
```scala mdoc:compile-only
|
|
891
|
+
import zio.blocks.schema.json.{Json, JsonPatch}
|
|
892
|
+
|
|
893
|
+
val patch1 = JsonPatch.diff(
|
|
894
|
+
Json.parseUnsafe("""{"x": 1}"""),
|
|
895
|
+
Json.parseUnsafe("""{"x": 2}""")
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
val patch2 = JsonPatch.diff(
|
|
899
|
+
Json.parseUnsafe("""{"x": 2}"""),
|
|
900
|
+
Json.parseUnsafe("""{"x": 2, "y": 3}""")
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
// Compose patches - applies patch1, then patch2
|
|
904
|
+
val combined = patch1 ++ patch2
|
|
905
|
+
|
|
906
|
+
// Apply the combined patch
|
|
907
|
+
val result = combined(Json.parseUnsafe("""{"x": 1}"""))
|
|
908
|
+
// Right({"x": 2, "y": 3})
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### Converting to DynamicPatch
|
|
912
|
+
|
|
913
|
+
`JsonPatch` can be converted to and from `DynamicPatch` for interoperability with the typed patching system:
|
|
914
|
+
|
|
915
|
+
```scala mdoc:compile-only
|
|
916
|
+
import zio.blocks.schema.json.JsonPatch
|
|
917
|
+
import zio.blocks.schema.patch.DynamicPatch
|
|
918
|
+
import zio.blocks.schema.SchemaError
|
|
919
|
+
|
|
920
|
+
val jsonPatch: JsonPatch = ???
|
|
921
|
+
|
|
922
|
+
// Convert to DynamicPatch
|
|
923
|
+
val dynamicPatch: DynamicPatch = jsonPatch.toDynamicPatch
|
|
924
|
+
|
|
925
|
+
// Convert from DynamicPatch (may fail for unsupported operations)
|
|
926
|
+
val restored: Either[SchemaError, JsonPatch] = JsonPatch.fromDynamicPatch(dynamicPatch)
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
## Conversion to DynamicValue
|
|
930
|
+
|
|
931
|
+
Convert JSON to ZIO Blocks' semi-structured `DynamicValue`:
|
|
932
|
+
|
|
933
|
+
```scala mdoc:compile-only
|
|
934
|
+
import zio.blocks.schema.json.Json
|
|
935
|
+
import zio.blocks.schema.DynamicValue
|
|
936
|
+
|
|
937
|
+
val json = Json.parseUnsafe("""{"name": "Alice"}""")
|
|
938
|
+
|
|
939
|
+
val dynamic: DynamicValue = json.toDynamicValue
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
This enables interoperability with other ZIO Blocks formats (Avro, TOON, etc.).
|
|
943
|
+
|
|
944
|
+
## Error Handling
|
|
945
|
+
|
|
946
|
+
### SchemaError
|
|
947
|
+
|
|
948
|
+
Errors include path information for debugging:
|
|
949
|
+
|
|
950
|
+
```scala mdoc:compile-only
|
|
951
|
+
import zio.blocks.schema.json.Json
|
|
952
|
+
import zio.blocks.schema.SchemaError
|
|
953
|
+
|
|
954
|
+
val json = Json.parseUnsafe("""{"users": [{"name": "Alice"}]}""")
|
|
955
|
+
|
|
956
|
+
val result = json.get("users")(5).get("name").as[String]
|
|
957
|
+
// Left(SchemaError: Index 5 out of bounds at path $.users[5])
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
### Error Properties
|
|
961
|
+
|
|
962
|
+
```scala mdoc:compile-only
|
|
963
|
+
import zio.blocks.schema.SchemaError
|
|
964
|
+
import zio.blocks.schema.DynamicOptic
|
|
965
|
+
|
|
966
|
+
val error: SchemaError = ???
|
|
967
|
+
|
|
968
|
+
error.message // Error description
|
|
969
|
+
error.errors.head.source // DynamicOptic path to error location
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
## Cross-Platform Support
|
|
973
|
+
|
|
974
|
+
The `Json` type works across all platforms:
|
|
975
|
+
|
|
976
|
+
- **JVM** - Full functionality
|
|
977
|
+
- **Scala.js** - Browser and Node.js
|
|
978
|
+
|
|
979
|
+
String interpolators use compile-time validation that works on all platforms.
|