Add JsonReader.init(..., requireAllFields: bool)

This commit is contained in:
Zahary Karadjov 2021-08-11 20:00:34 +03:00
parent 652099a959
commit 4f3775ddf4
2 changed files with 103 additions and 2 deletions

View File

@ -14,6 +14,7 @@ type
JsonReader*[Flavor = DefaultFlavor] = object JsonReader*[Flavor = DefaultFlavor] = object
lexer*: JsonLexer lexer*: JsonLexer
allowUnknownFields: bool allowUnknownFields: bool
requireAllFields: bool
JsonReaderError* = object of JsonError JsonReaderError* = object of JsonError
line*, col*: int line*, col*: int
@ -46,6 +47,9 @@ type
UnexpectedValueError* = object of JsonReaderError UnexpectedValueError* = object of JsonReaderError
IncompleteObjectError* = object of JsonReaderError
objectType: cstring
IntOverflowError* = object of JsonReaderError IntOverflowError* = object of JsonReaderError
isNegative: bool isNegative: bool
absIntVal: uint64 absIntVal: uint64
@ -79,6 +83,9 @@ method formatMsg*(err: ref IntOverflowError, filename: string): string =
method formatMsg*(err: ref UnexpectedValueError, filename: string): string = method formatMsg*(err: ref UnexpectedValueError, filename: string): string =
tryFmt: fmt"{filename}({err.line}, {err.col}) {err.msg}" 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) = proc assignLineNumber*(ex: ref JsonReaderError, r: JsonReader) =
ex.line = r.lexer.line ex.line = r.lexer.line
ex.col = r.lexer.tokenStartCol ex.col = r.lexer.tokenStartCol
@ -111,6 +118,12 @@ proc raiseUnexpectedField*(r: JsonReader, fieldName, deserializedType: cstring)
ex.deserializedType = deserializedType ex.deserializedType = deserializedType
raise ex 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, proc handleReadException*(r: JsonReader,
Record: type, Record: type,
fieldName: string, fieldName: string,
@ -125,8 +138,10 @@ proc handleReadException*(r: JsonReader,
proc init*(T: type JsonReader, proc init*(T: type JsonReader,
stream: InputStream, stream: InputStream,
mode = defaultJsonMode, mode = defaultJsonMode,
allowUnknownFields = false): T = allowUnknownFields = false,
requireAllFields = false): T =
result.allowUnknownFields = allowUnknownFields result.allowUnknownFields = allowUnknownFields
result.requireAllFields = requireAllFields
result.lexer = JsonLexer.init(stream, mode) result.lexer = JsonLexer.init(stream, mode)
result.lexer.next() result.lexer.next()
@ -500,7 +515,9 @@ proc readValue*[T](r: var JsonReader, value: var T)
type T = type(value) type T = type(value)
r.skipToken tkCurlyLe r.skipToken tkCurlyLe
when T.totalSerializedFields > 0: const expectedFields = T.totalSerializedFields
var readFields = 0
when expectedFields > 0:
let fields = T.fieldReadersTable(ReaderType) let fields = T.fieldReadersTable(ReaderType)
var expectedFieldPos = 0 var expectedFieldPos = 0
while r.lexer.tok == tkString: while r.lexer.tok == tkString:
@ -513,6 +530,7 @@ proc readValue*[T](r: var JsonReader, value: var T)
r.skipToken tkColon r.skipToken tkColon
if reader != nil: if reader != nil:
reader(value, r) reader(value, r)
inc readFields
elif r.allowUnknownFields: elif r.allowUnknownFields:
r.skipSingleJsValue() r.skipSingleJsValue()
else: else:
@ -523,6 +541,10 @@ proc readValue*[T](r: var JsonReader, value: var T)
else: else:
break break
if r.requireAllFields and readFields != expectedFields:
const typeName = typetraits.name(T)
r.raiseIncompleteObject(typeName)
r.skipToken tkCurlyRi r.skipToken tkCurlyRi
else: else:

View File

@ -132,6 +132,85 @@ suite "toJson tests":
let shouldNotDecode = Json.decode(json, Simple) let shouldNotDecode = Json.decode(json, Simple)
echo "This should not have decoded ", shouldNotDecode 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": test "arrays are printed correctly":
var x = HoldsArray(data: @[1, 2, 3, 4]) var x = HoldsArray(data: @[1, 2, 3, 4])