Skip to content

Commit

Permalink
Add JsonReader.init(..., requireAllFields: bool)
Browse files Browse the repository at this point in the history
  • Loading branch information
zah committed Aug 11, 2021
1 parent 652099a commit 4f3775d
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 2 deletions.
26 changes: 24 additions & 2 deletions json_serialization/reader.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type
JsonReader*[Flavor = DefaultFlavor] = object
lexer*: JsonLexer
allowUnknownFields: bool
requireAllFields: bool

JsonReaderError* = object of JsonError
line*, col*: int
Expand Down Expand Up @@ -46,6 +47,9 @@ type

UnexpectedValueError* = object of JsonReaderError

IncompleteObjectError* = object of JsonReaderError
objectType: cstring

IntOverflowError* = object of JsonReaderError
isNegative: bool
absIntVal: uint64
Expand Down Expand Up @@ -79,6 +83,9 @@ method formatMsg*(err: ref IntOverflowError, filename: string): string =
method formatMsg*(err: ref UnexpectedValueError, filename: string): string =
tryFmt: fmt"{filename}({err.line}, {err.col}) {err.msg}"

method formatMsg*(err: ref IncompleteObjectError, filename: string): string =
tryFmt: fmt"{filename}({err.line}, {err.col}) Not all required fields were specified when reading '{err.objectType}'"

proc assignLineNumber*(ex: ref JsonReaderError, r: JsonReader) =
ex.line = r.lexer.line
ex.col = r.lexer.tokenStartCol
Expand Down Expand Up @@ -111,6 +118,12 @@ proc raiseUnexpectedField*(r: JsonReader, fieldName, deserializedType: cstring)
ex.deserializedType = deserializedType
raise ex

proc raiseIncompleteObject*(r: JsonReader, objectType: cstring) {.noreturn.} =
var ex = new IncompleteObjectError
ex.assignLineNumber(r)
ex.objectType = objectType
raise ex

proc handleReadException*(r: JsonReader,
Record: type,
fieldName: string,
Expand All @@ -125,8 +138,10 @@ proc handleReadException*(r: JsonReader,
proc init*(T: type JsonReader,
stream: InputStream,
mode = defaultJsonMode,
allowUnknownFields = false): T =
allowUnknownFields = false,
requireAllFields = false): T =
result.allowUnknownFields = allowUnknownFields
result.requireAllFields = requireAllFields
result.lexer = JsonLexer.init(stream, mode)
result.lexer.next()

Expand Down Expand Up @@ -500,7 +515,9 @@ proc readValue*[T](r: var JsonReader, value: var T)
type T = type(value)
r.skipToken tkCurlyLe

when T.totalSerializedFields > 0:
const expectedFields = T.totalSerializedFields
var readFields = 0
when expectedFields > 0:
let fields = T.fieldReadersTable(ReaderType)
var expectedFieldPos = 0
while r.lexer.tok == tkString:
Expand All @@ -513,6 +530,7 @@ proc readValue*[T](r: var JsonReader, value: var T)
r.skipToken tkColon
if reader != nil:
reader(value, r)
inc readFields
elif r.allowUnknownFields:
r.skipSingleJsValue()
else:
Expand All @@ -523,6 +541,10 @@ proc readValue*[T](r: var JsonReader, value: var T)
else:
break

if r.requireAllFields and readFields != expectedFields:
const typeName = typetraits.name(T)
r.raiseIncompleteObject(typeName)

r.skipToken tkCurlyRi

else:
Expand Down
79 changes: 79 additions & 0 deletions tests/test_serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,85 @@ suite "toJson tests":
let shouldNotDecode = Json.decode(json, Simple)
echo "This should not have decoded ", shouldNotDecode

test "all fields are required and present":
let json = dedent"""
{
"x": 20,
"distance": 10,
"y": "y value"
}
"""

let decoded = Json.decode(json, Simple, requireAllFields = true)

check:
decoded.x == 20
decoded.y == "y value"
decoded.distance.int == 10

test "all fields were required, but not all were provided":
let json = dedent"""
{
"x": -20,
"distance": 10
}
"""

expect IncompleteObjectError:
let shouldNotDecode = Json.decode(json, Simple, requireAllFields = true)
echo "This should not have decoded ", shouldNotDecode

test "all fields were required, but not all were provided (additional fields present instead)":
let json = dedent"""
{
"futureBool": false,
"y": "y value",
"futureObject": {"a": -1, "b": [1, 2.0, 3.1], "c": null, "d": true},
"distance": 10
}
"""

expect IncompleteObjectError:
let shouldNotDecode = Json.decode(json, Simple,
requireAllFields = true,
allowUnknownFields = true)
echo "This should not have decoded ", shouldNotDecode

test "all fields were required, but none were provided":
let json = "{}"

expect IncompleteObjectError:
let shouldNotDecode = Json.decode(json, Simple, requireAllFields = true)
echo "This should not have decoded ", shouldNotDecode

test "all fields are required and provided, and additional ones are present":
let json = dedent"""
{
"x": 20,
"distance": 10,
"futureBool": false,
"y": "y value",
"futureObject": {"a": -1, "b": [1, 2.0, 3.1], "c": null, "d": true},
}
"""

let decoded = try:
Json.decode(json, Simple, requireAllFields = true, allowUnknownFields = true)
except SerializationError as err:
checkpoint "Unexpected deserialization failure: " & err.formatMsg("<input>")
raise

check:
decoded.x == 20
decoded.y == "y value"
decoded.distance.int == 10

expect UnexpectedField:
let shouldNotDecode = Json.decode(json, Simple,
requireAllFields = true,
allowUnknownFields = false)
echo "This should not have decoded ", shouldNotDecode

test "arrays are printed correctly":
var x = HoldsArray(data: @[1, 2, 3, 4])

Expand Down

0 comments on commit 4f3775d

Please sign in to comment.