Skip to content

Commit bc49ad8

Browse files
authored
Add array streaming helpers (#115)
An implementation of #112 that introduces `beginArray`/`endArray` for the streaming creation of arrays. In order to accomodate the need for intra-element plumbing, we add `begin`/`end` marker calls to the `writeValue` implementation which helps the writer keep track of each value being written and therefore allows it to inject the correct delimiters and indents. Here's an example of writing a `writeValue` overload that writes an array nested in an object: ```nim proc writeValue(w: var JsonWriter, t: MyType) = w.beginArray() for i in 0 ..< t.children: writer.beginObject() writer.writeMember("id", i) writer.writeMember("name", "item" & t.childName[i]) writer.endObject() writer.endArray() ``` Similar to the existing `beginRecord`/`endRecord` fields we add `beginArray` and `endArray` - we also take the opportunity to name `beginObject` according to its json-spec-derived name. The old name remains available. This change introduces a backwards-compatibility break for custom writers that try to access the stream directly: they now have to delimit their value writing with `w.streamElement(s): s.write ...` where `s` is the stream variable. The block template enforces begin/end markers on behalf of the writer. Further examples are available in the documentation. With this change, we also deprecate workarounds like `fieldWritten` and `endRecordField` since a regular replacement exists in the form of consistent begin/end pairs. * doc fixes * make beginElement/endElement private Should not be called directly as `writeValue` / `streamElement` take care of it.
1 parent 7c1fa80 commit bc49ad8

File tree

9 files changed

+454
-252
lines changed

9 files changed

+454
-252
lines changed

docs/examples/reference0.nim

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@ type
1818
var conf = defaultJsonReaderConf
1919
conf.nestedDepthLimit = 0
2020

21-
let native =
22-
Json.decode(rawJson, NimServer, flags = defaultJsonReaderFlags, conf = conf)
23-
2421
# decode into native Nim
25-
#let native = Json.decode(rawJson, NimServer)
22+
let native = Json.decode(rawJson, NimServer)
2623

2724
# decode into mixed Nim + JsonValueRef
2825
let mixed = Json.decode(rawJson, MixedServer)

docs/examples/streamwrite0.nim

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import json_serialization, faststreams/outputs
2+
3+
let file = fileOutput("output.json")
4+
var writer = JsonWriter[DefaultFlavor].init(file, pretty = true)
5+
6+
writer.beginArray()
7+
8+
for i in 0 ..< 2:
9+
writer.beginObject()
10+
11+
writer.writeMember("id", i)
12+
writer.writeMember("name", "item" & $i)
13+
14+
writer.endObject()
15+
16+
writer.endArray()
17+
18+
file.close()

docs/examples/streamwrite1.nim

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import json_serialization, faststreams/outputs
2+
3+
let file = fileOutput("output.json")
4+
var writer = JsonWriter[DefaultFlavor].init(file)
5+
6+
# ANCHOR: Nesting
7+
writer.writeObject:
8+
writer.writeMember("status", "ok")
9+
writer.writeName("data")
10+
writer.writeArray:
11+
for i in 0 ..< 2:
12+
writer.writeObject:
13+
writer.writeMember("id", i)
14+
writer.writeMember("name", "item" & $i)
15+
# ANCHOR_END: Nesting
16+
17+
file.close()

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# User guide
44

55
- [Getting started](./getting_started.md)
6+
- [Streaming](./streaming.md)
67
- [Reference](./reference.md)
78

89
# Developer guide

docs/src/reference.md

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ You can adjust these defaults to suit your needs:
8383

8484
### Common API
8585

86-
Similar to parsing, the [common serialization API]() is used to produce JSON documents.
86+
Similar to parsing, the [common serialization API](https://github.com/status-im/nim-serialization?tab=readme-ov-file#common-api) is used to produce JSON documents.
8787

8888
```nim
8989
{{#include ../examples/reference0.nim:Encode}}
@@ -246,24 +246,7 @@ readRecordValue[T](r: var JsonReader, value: var T)
246246

247247
## JsonWriter Helper Procedures
248248

249-
```nim
250-
beginRecord(w: var JsonWriter, T: type)
251-
beginRecord(w: var JsonWriter)
252-
endRecord(w: var JsonWriter)
253-
254-
writeObject(w: var JsonWriter, T: type)
255-
writeObject(w: var JsonWriter)
256-
257-
writeFieldName(w: var JsonWriter, name: string)
258-
writeField(w: var JsonWriter, name: string, value: auto)
259-
260-
iterator stepwiseArrayCreation[C](w: var JsonWriter, collection: C): auto
261-
writeIterable(w: var JsonWriter, collection: auto)
262-
writeArray[T](w: var JsonWriter, elements: openArray[T])
263-
264-
writeNumber[F,T](w: var JsonWriter[F], value: JsonNumber[T])
265-
writeJsonValueRef[F,T](w: var JsonWriter[F], value: JsonValueRef[T])
266-
```
249+
See the [API reference](./api/json_serialization/writer.html)
267250

268251
## Enums
269252

docs/src/streaming.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Streaming
2+
3+
`JsonWriter` can be used to incrementally write JSON data.
4+
5+
Incremental processing is ideal for large documents or when you want to avoid building the entire JSON structure in memory.
6+
7+
<!-- toc -->
8+
9+
## Writing
10+
11+
You can use `JsonWriter` to write JSON objects, arrays, and values step by step, directly to a file or any output stream.
12+
13+
The process is similar to when you override `writeValue` to provide custom serialization.
14+
15+
### Example: Writing a JSON Array of Objects
16+
17+
Suppose you want to write a large array of objects to a file, one at a time:
18+
19+
```nim
20+
{{#include ../examples/streamwrite0.nim}}
21+
```
22+
23+
Resulting file (`output.json`):
24+
```json
25+
[
26+
{
27+
"id": 0,
28+
"name": "item0"
29+
},
30+
{
31+
"id": 1,
32+
"name": "item1"
33+
}
34+
]
35+
```
36+
37+
### Example: Writing Nested Structures
38+
39+
Objects and arrays can be nested arbitrarily.
40+
41+
Here is the same array of JSON objects, nested in an envelope containing an additional `status` field.
42+
43+
Instead of manually placing `begin`/`end` pairs, we're using the convenience helpers `writeObject` and `writeArrayMember`, along with `writeElement` to manage the required element markers:
44+
45+
```ni
46+
{{#include ../examples/streamwrite1.nim:Nesting}}
47+
```
48+
49+
This produces a the following output - notice the more compact representation when `pretty = true` is not used:
50+
```json
51+
{"status":"ok","data":[{"id":0,"name":"item0"},{"id":1,"name":"item1"}]}
52+
```

json_serialization/types.nim

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
# This file may not be copied, modified, or distributed except according to
88
# those terms.
99

10+
{.push gcsafe, raises: [].}
11+
1012
import
1113
std/tables,
1214
serialization/errors
@@ -18,13 +20,13 @@ export
1820
type
1921
JsonError* = object of SerializationError
2022

21-
# This is a special type to parse whatever
22-
# json value into string.
2323
JsonString* = distinct string
24+
## A string containing valid JSON.
25+
## Used to preserve and pass on parts of a JSON document to another parser
26+
## or layer without interpreting it further
2427

25-
# This is a special type to parse whatever
26-
# json value into nothing/skip it.
2728
JsonVoid* = object
29+
## Marker used for skipping a JSON value during parsing
2830

2931
JsonSign* {.pure.} = enum
3032
None
@@ -63,11 +65,11 @@ type
6365
stringLengthLimit*: int
6466

6567
JsonValueKind* {.pure.} = enum
66-
String,
67-
Number,
68-
Object,
69-
Array,
70-
Bool,
68+
String
69+
Number
70+
Object
71+
Array
72+
Bool
7173
Null
7274

7375
JsonObjectType*[T: string or uint64] = OrderedTable[string, JsonValueRef[T]]
@@ -88,7 +90,6 @@ type
8890
of JsonValueKind.Null:
8991
discard
9092

91-
9293
const
9394
minPortableInt* = -9007199254740991 # -2**53 + 1
9495
maxPortableInt* = 9007199254740991 # +2**53 - 1
@@ -110,8 +111,6 @@ const
110111
stringLengthLimit: 0,
111112
)
112113

113-
{.push gcsafe, raises: [].}
114-
115114
template `==`*(lhs, rhs: JsonString): bool =
116115
string(lhs) == string(rhs)
117116

@@ -172,4 +171,7 @@ func `==`*(lhs, rhs: JsonValueRef): bool =
172171
of JsonValueKind.Null:
173172
true
174173

174+
template `$`*(s: JsonString): string =
175+
string(s)
176+
175177
{.pop.}

0 commit comments

Comments
 (0)