Skip to content

Commit 991bd85

Browse files
authored
fix reading of empty object (#118)
When an object has no fields, it must still consume the contents, either skipping or raising Bonus: * fewer enumAllSerializedFields for a tiny compilation speed improvement * make raises more uniform
1 parent 187ecf2 commit 991bd85

File tree

16 files changed

+115
-88
lines changed

16 files changed

+115
-88
lines changed

json_serialization/format.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# json-serialization
2-
# Copyright (c) 2019-2023 Status Research & Development GmbH
2+
# Copyright (c) 2019-2025 Status Research & Development GmbH
33
# Licensed under either of
44
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
55
# * MIT license ([LICENSE-MIT](LICENSE-MIT))

json_serialization/lexer.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# json-serialization
2-
# Copyright (c) 2019-2023 Status Research & Development GmbH
2+
# Copyright (c) 2019-2025 Status Research & Development GmbH
33
# Licensed under either of
44
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
55
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
66
# at your option.
77
# This file may not be copied, modified, or distributed except according to
88
# those terms.
99

10+
{.push raises: [], gcsafe.}
11+
1012
import
1113
std/[json, unicode],
1214
faststreams/inputs,
@@ -72,8 +74,6 @@ type
7274
tokenStart: int
7375
depthLimit: int
7476

75-
{.push gcsafe, raises: [].}
76-
7777
# ------------------------------------------------------------------------------
7878
# Private helpers
7979
# ------------------------------------------------------------------------------

json_serialization/parser.nim

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# json-serialization
2-
# Copyright (c) 2019-2023 Status Research & Development GmbH
2+
# Copyright (c) 2019-2025 Status Research & Development GmbH
33
# Licensed under either of
44
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
55
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
@@ -8,6 +8,7 @@
88
# those terms.
99

1010
{.experimental: "notnil".}
11+
{.push raises: [], gcsafe.}
1112

1213
import
1314
./reader_desc,
@@ -18,8 +19,6 @@ from json import JsonNode, JsonNodeKind, escapeJson, parseJson
1819
export
1920
reader_desc
2021

21-
{.push gcsafe, raises: [].}
22-
2322
type
2423
NumberPart* = enum
2524
SignPart
@@ -59,7 +58,7 @@ template checkError*(r: var JsonReader) =
5958
r.raiseParserError()
6059

6160
proc tokKind*(r: var JsonReader): JsonValueKind
62-
{.gcsafe, raises: [IOError, JsonReaderError].} =
61+
{.raises: [IOError, JsonReaderError].} =
6362
result = r.lex.tokKind
6463
r.checkError
6564

@@ -68,7 +67,7 @@ proc tokKind*(r: var JsonReader): JsonValueKind
6867
# ------------------------------------------------------------------------------
6968

7069
proc customIntHandler*(r: var JsonReader; handler: CustomIntHandler)
71-
{.gcsafe, raises: [IOError, JsonReaderError].} =
70+
{.raises: [IOError, JsonReaderError].} =
7271
## Apply the `handler` argument function for parsing only integer part
7372
## of JsonNumber
7473
# TODO: remove temporary token
@@ -83,7 +82,7 @@ proc customIntHandler*(r: var JsonReader; handler: CustomIntHandler)
8382
handler(ord(c) - ord('0'))
8483

8584
proc customNumberHandler*(r: var JsonReader; handler: CustomNumberHandler)
86-
{.gcsafe, raises: [IOError, JsonReaderError].} =
85+
{.raises: [IOError, JsonReaderError].} =
8786
## Apply the `handler` argument function for parsing complete JsonNumber
8887
# TODO: remove temporary token
8988
if r.tokKind != JsonValueKind.Number:
@@ -101,7 +100,7 @@ proc customNumberHandler*(r: var JsonReader; handler: CustomNumberHandler)
101100
handler(ExponentPart, ord(c) - ord('0'))
102101

103102
proc customStringHandler*(r: var JsonReader; limit: int; handler: CustomStringHandler)
104-
{.gcsafe, raises: [IOError, JsonReaderError].} =
103+
{.raises: [IOError, JsonReaderError].} =
105104
## Apply the `handler` argument function for parsing a String type
106105
## value.
107106
# TODO: remove temporary token
@@ -167,32 +166,32 @@ template customStringValueIt*(r: var JsonReader; body: untyped) =
167166
# ------------------------------------------------------------------------------
168167

169168
proc parseString*(r: var JsonReader, limit: int): string
170-
{.gcsafe, raises: [IOError, JsonReaderError].} =
169+
{.raises: [IOError, JsonReaderError].} =
171170
if r.tokKind != JsonValueKind.String:
172171
r.raiseParserError(errStringExpected)
173172
r.lex.scanString(result, limit)
174173
r.checkError
175174

176175
proc parseString*(r: var JsonReader): string
177-
{.gcsafe, raises: [IOError, JsonReaderError].} =
176+
{.raises: [IOError, JsonReaderError].} =
178177
r.parseString(r.lex.conf.stringLengthLimit)
179178

180179
proc parseBool*(r: var JsonReader): bool
181-
{.gcsafe, raises: [IOError, JsonReaderError].} =
180+
{.raises: [IOError, JsonReaderError].} =
182181
if r.tokKind != JsonValueKind.Bool:
183182
r.raiseParserError(errBoolExpected)
184183
result = r.lex.scanBool()
185184
r.checkError
186185

187186
proc parseNull*(r: var JsonReader)
188-
{.gcsafe, raises: [IOError, JsonReaderError].} =
187+
{.raises: [IOError, JsonReaderError].} =
189188
if r.tokKind != JsonValueKind.Null:
190189
r.raiseParserError(errNullExpected)
191190
r.lex.scanNull()
192191
r.checkError
193192

194193
proc parseNumberImpl[F,T](r: var JsonReader[F]): JsonNumber[T]
195-
{.gcsafe, raises: [IOError, JsonReaderError].} =
194+
{.raises: [IOError, JsonReaderError].} =
196195
if r.tokKind != JsonValueKind.Number:
197196
r.raiseParserError(errNumberExpected)
198197
r.lex.scanNumber(result)
@@ -206,14 +205,14 @@ template parseNumber*(r: var JsonReader, T: type): auto =
206205
parseNumberImpl[F.Flavor, T](r)
207206

208207
proc parseNumber*(r: var JsonReader, val: var JsonNumber)
209-
{.gcsafe, raises: [IOError, JsonReaderError].} =
208+
{.raises: [IOError, JsonReaderError].} =
210209
if r.tokKind != JsonValueKind.Number:
211210
r.raiseParserError(errNumberExpected)
212211
r.lex.scanNumber(val)
213212
r.checkError
214213

215214
proc toInt*(r: var JsonReader, val: JsonNumber, T: type SomeSignedInt, portable: bool): T
216-
{.gcsafe, raises: [JsonReaderError].}=
215+
{.raises: [JsonReaderError].}=
217216
if val.sign == JsonSign.Neg:
218217
if val.integer.uint64 > T.high.uint64 + 1:
219218
raiseIntOverflow(r, val.integer, true)
@@ -232,7 +231,7 @@ proc toInt*(r: var JsonReader, val: JsonNumber, T: type SomeSignedInt, portable:
232231
raiseIntOverflow(r, result.BiggestUInt, true)
233232

234233
proc toInt*(r: var JsonReader, val: JsonNumber, T: type SomeUnsignedInt, portable: bool): T
235-
{.gcsafe, raises: [IOError, JsonReaderError].}=
234+
{.raises: [IOError, JsonReaderError].}=
236235
if val.sign == JsonSign.Neg:
237236
raiseUnexpectedToken(r, etInt)
238237
if val.integer > T.high.uint64:
@@ -244,7 +243,7 @@ proc toInt*(r: var JsonReader, val: JsonNumber, T: type SomeUnsignedInt, portabl
244243
T(val.integer)
245244

246245
proc parseInt*(r: var JsonReader, T: type SomeInteger, portable: bool = false): T
247-
{.gcsafe, raises: [IOError, JsonReaderError].} =
246+
{.raises: [IOError, JsonReaderError].} =
248247
if r.tokKind != JsonValueKind.Number:
249248
r.raiseParserError(errNumberExpected)
250249
var val: JsonNumber[uint64]
@@ -255,7 +254,7 @@ proc parseInt*(r: var JsonReader, T: type SomeInteger, portable: bool = false):
255254
r.toInt(val, T, portable)
256255

257256
proc toFloat*(r: var JsonReader, val: JsonNumber, T: type SomeFloat): T
258-
{.gcsafe, raises: [JsonReaderError].}=
257+
{.raises: [JsonReaderError].}=
259258
const
260259
powersOfTen = [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
261260
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
@@ -281,7 +280,7 @@ proc toFloat*(r: var JsonReader, val: JsonNumber, T: type SomeFloat): T
281280
result = result * powersOfTen[val.exponent]
282281

283282
proc parseFloat*(r: var JsonReader, T: type SomeFloat): T
284-
{.gcsafe, raises: [IOError, JsonReaderError].} =
283+
{.raises: [IOError, JsonReaderError].} =
285284
if r.tokKind != JsonValueKind.Number:
286285
r.raiseParserError(errNumberExpected)
287286
var val: JsonNumber[uint64]
@@ -290,7 +289,7 @@ proc parseFloat*(r: var JsonReader, T: type SomeFloat): T
290289
r.toFloat(val, T)
291290

292291
proc parseAsString*(r: var JsonReader, val: var string)
293-
{.gcsafe, raises: [IOError, JsonReaderError].} =
292+
{.raises: [IOError, JsonReaderError].} =
294293
case r.tokKind
295294
of JsonValueKind.String:
296295
escapeJson(r.parseString(), val)
@@ -334,13 +333,13 @@ proc parseAsString*(r: var JsonReader, val: var string)
334333
val.add "null"
335334

336335
proc parseAsString*(r: var JsonReader): JsonString
337-
{.gcsafe, raises: [IOError, JsonReaderError].} =
336+
{.raises: [IOError, JsonReaderError].} =
338337
var val: string
339338
r.parseAsString(val)
340339
val.JsonString
341340

342341
proc parseValueImpl[F,T](r: var JsonReader[F]): JsonValueRef[T]
343-
{.gcsafe, raises: [IOError, JsonReaderError].} =
342+
{.raises: [IOError, JsonReaderError].} =
344343
r.lex.scanValue(result)
345344
r.checkError
346345

@@ -352,7 +351,7 @@ template parseValue*(r: var JsonReader, T: type): auto =
352351
parseValueImpl[F.Flavor, T](r)
353352

354353
proc parseValue*(r: var JsonReader, val: var JsonValueRef)
355-
{.gcsafe, raises: [IOError, JsonReaderError].} =
354+
{.raises: [IOError, JsonReaderError].} =
356355
r.lex.scanValue(val)
357356
r.checkError
358357

@@ -443,10 +442,10 @@ template parseObjectCustomKey*(r: var JsonReader, keyAction: untyped, body: unty
443442
# ------------------------------------------------------------------------------
444443

445444
proc parseJsonNode*(r: var JsonReader): JsonNode
446-
{.gcsafe, raises: [IOError, JsonReaderError].}
445+
{.raises: [IOError, JsonReaderError].}
447446

448447
proc readJsonNodeField(r: var JsonReader, field: var JsonNode)
449-
{.gcsafe, raises: [IOError, JsonReaderError].} =
448+
{.raises: [IOError, JsonReaderError].} =
450449
if field.isNil.not:
451450
r.raiseUnexpectedValue("Unexpected duplicated field name")
452451
field = r.parseJsonNode()

json_serialization/pkg/results.nim

Lines changed: 4 additions & 1 deletion
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 raises: [], gcsafe.}
11+
1012
import
1113
pkg/results, ../../json_serialization/[reader, writer, lexer]
1214

@@ -25,7 +27,8 @@ proc writeValue*[T](
2527
else:
2628
writer.writeValue JsonString("null")
2729

28-
proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) =
30+
proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) {.
31+
raises: [IOError, SerializationError].} =
2932
mixin readValue
3033

3134
if reader.tokKind == JsonValueKind.Null:

json_serialization/reader.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# json-serialization
2-
# Copyright (c) 2019-2023 Status Research & Development GmbH
2+
# Copyright (c) 2019-2025 Status Research & Development GmbH
33
# Licensed under either of
44
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
55
# * MIT license ([LICENSE-MIT](LICENSE-MIT))

json_serialization/reader_desc.nim

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# json-serialization
2-
# Copyright (c) 2019-2023 Status Research & Development GmbH
2+
# Copyright (c) 2019-2025 Status Research & Development GmbH
33
# Licensed under either of
44
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
55
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
@@ -8,6 +8,7 @@
88
# those terms.
99

1010
{.experimental: "notnil".}
11+
{.push raises: [], gcsafe.}
1112

1213
import
1314
std/[strformat],
@@ -64,8 +65,6 @@ type
6465

6566
Json.setReader JsonReader
6667

67-
{.push gcsafe, raises: [].}
68-
6968
func valueStr(err: ref IntOverflowError): string =
7069
if err.isNegative:
7170
result.add '-'
@@ -75,32 +74,25 @@ template tryFmt(expr: untyped): string =
7574
try: expr
7675
except CatchableError as err: err.msg
7776

78-
method formatMsg*(err: ref JsonReaderError, filename: string):
79-
string {.gcsafe, raises: [].} =
77+
method formatMsg*(err: ref JsonReaderError, filename: string): string =
8078
tryFmt: fmt"{filename}({err.line}, {err.col}) Error while reading json file: {err.msg}"
8179

82-
method formatMsg*(err: ref UnexpectedField, filename: string):
83-
string {.gcsafe, raises: [].} =
80+
method formatMsg*(err: ref UnexpectedField, filename: string): string =
8481
tryFmt: fmt"{filename}({err.line}, {err.col}) Unexpected field '{err.encounteredField}' while deserializing {err.deserializedType}"
8582

86-
method formatMsg*(err: ref UnexpectedTokenError, filename: string):
87-
string {.gcsafe, raises: [].} =
83+
method formatMsg*(err: ref UnexpectedTokenError, filename: string): string =
8884
tryFmt: fmt"{filename}({err.line}, {err.col}) Unexpected token '{err.encountedToken}' in place of '{err.expectedToken}'"
8985

90-
method formatMsg*(err: ref GenericJsonReaderError, filename: string):
91-
string {.gcsafe, raises: [].} =
86+
method formatMsg*(err: ref GenericJsonReaderError, filename: string): string =
9287
tryFmt: fmt"{filename}({err.line}, {err.col}) Exception encountered while deserializing '{err.deserializedField}': [{err.innerException.name}] {err.innerException.msg}"
9388

94-
method formatMsg*(err: ref IntOverflowError, filename: string):
95-
string {.gcsafe, raises: [].} =
89+
method formatMsg*(err: ref IntOverflowError, filename: string): string =
9690
tryFmt: fmt"{filename}({err.line}, {err.col}) The value '{err.valueStr}' is outside of the allowed range"
9791

98-
method formatMsg*(err: ref UnexpectedValueError, filename: string):
99-
string {.gcsafe, raises: [].} =
92+
method formatMsg*(err: ref UnexpectedValueError, filename: string): string =
10093
tryFmt: fmt"{filename}({err.line}, {err.col}) {err.msg}"
10194

102-
method formatMsg*(err: ref IncompleteObjectError, filename: string):
103-
string {.gcsafe, raises: [].} =
95+
method formatMsg*(err: ref IncompleteObjectError, filename: string): string =
10496
tryFmt: fmt"{filename}({err.line}, {err.col}) Not all required fields were specified when reading '{err.objectType}'"
10597

10698
func assignLineNumber*(ex: ref JsonReaderError, lex: JsonLexer) =
@@ -184,13 +176,13 @@ template handleReadException*(r: JsonReader,
184176
proc init*(T: type JsonReader,
185177
stream: InputStream,
186178
flags: JsonReaderFlags,
187-
conf: JsonReaderConf = defaultJsonReaderConf): T {.raises: [].} =
179+
conf: JsonReaderConf = defaultJsonReaderConf): T =
188180
result.lex = JsonLexer.init(stream, flags, conf)
189181

190182
proc init*(T: type JsonReader,
191183
stream: InputStream,
192184
allowUnknownFields = false,
193-
requireAllFields = false): T {.raises: [].} =
185+
requireAllFields = false): T =
194186
mixin flavorAllowsUnknownFields, flavorRequiresAllFields
195187
type Flavor = T.Flavor
196188

0 commit comments

Comments
 (0)