Add flavor feature: reader will skip null fields

This commit is contained in:
jangko 2024-01-15 10:28:23 +07:00
parent 9c74b885ea
commit 0db6307eb3
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
5 changed files with 76 additions and 9 deletions

View File

@ -87,6 +87,7 @@ You can use `useDefaultSerializationIn` to add serializers of a flavor to a spec
requireAllFields = true
omitOptionalFields = true
allowUnknownFields = true
skipNullFields = false
```
```Nim

View File

@ -24,6 +24,7 @@ template flavorUsesAutomaticObjectSerialization*(T: type DefaultFlavor): bool =
template flavorOmitsOptionalFields*(T: type DefaultFlavor): bool = false
template flavorRequiresAllFields*(T: type DefaultFlavor): bool = false
template flavorAllowsUnknownFields*(T: type DefaultFlavor): bool = false
template flavorSkipNullFields*(T: type DefaultFlavor): bool = false
# We create overloads of these traits to force the mixin treatment of the symbols
type DummyFlavor* = object
@ -31,13 +32,15 @@ template flavorUsesAutomaticObjectSerialization*(T: type DummyFlavor): bool = tr
template flavorOmitsOptionalFields*(T: type DummyFlavor): bool = false
template flavorRequiresAllFields*(T: type DummyFlavor): bool = false
template flavorAllowsUnknownFields*(T: type DummyFlavor): bool = false
template flavorSkipNullFields*(T: type DummyFlavor): bool = false
template createJsonFlavor*(FlavorName: untyped,
mimeTypeValue = "application/json",
automaticObjectSerialization = false,
requireAllFields = true,
omitOptionalFields = true,
allowUnknownFields = true) {.dirty.} =
allowUnknownFields = true,
skipNullFields = false) {.dirty.} =
type FlavorName* = object
template Reader*(T: type FlavorName): type = Reader(Json, FlavorName)
@ -49,3 +52,4 @@ template createJsonFlavor*(FlavorName: untyped,
template flavorOmitsOptionalFields*(T: type FlavorName): bool = omitOptionalFields
template flavorRequiresAllFields*(T: type FlavorName): bool = requireAllFields
template flavorAllowsUnknownFields*(T: type FlavorName): bool = allowUnknownFields
template flavorSkipNullFields*(T: type FlavorName): bool = skipNullFields

View File

@ -580,7 +580,11 @@ proc scanString*[T](lex: var JsonLexer, val: var T, limit: int)
proc scanValue*[T](lex: var JsonLexer, val: var T)
{.gcsafe, raises: [IOError].}
proc tokKind*(lex: var JsonLexer): JsonValueKind
{.gcsafe, raises: [IOError].}
template parseObjectImpl*(lex: JsonLexer,
skipNullFields: static[bool],
actionInitial: untyped,
actionClosing: untyped,
actionComma: untyped,
@ -645,7 +649,13 @@ template parseObjectImpl*(lex: JsonLexer,
error(lex, errColonExpected, actionError)
lex.advance
actionValue
when skipNullFields:
if lex.tokKind() == JsonValueKind.Null:
lex.scanNull()
else:
actionValue
else:
actionValue
if not lex.ok: actionError
else:
error(lex, errStringExpected, actionError)
@ -657,7 +667,7 @@ proc scanObject*[T](lex: var JsonLexer, val: var T)
when T isnot (string or JsonVoid or JsonObjectType):
{.fatal: "`scanObject` only accepts `string` or `JsonVoid` or `JsonObjectType`".}
parseObjectImpl(lex):
parseObjectImpl(lex, false):
# initial action
when T is string:
val.add '{'

View File

@ -211,7 +211,7 @@ proc parseNumber*(r: var JsonReader, val: var JsonNumber)
r.raiseParserError(errNumberExpected)
r.lex.scanNumber(val)
r.checkError
proc toInt*(r: var JsonReader, val: JsonNumber, T: type SomeSignedInt, portable: bool): T
{.gcsafe, raises: [JsonReaderError].}=
if val.sign == JsonSign.Neg:
@ -291,6 +291,12 @@ proc parseFloat*(r: var JsonReader, T: type SomeFloat): T
proc parseAsString*(r: var JsonReader, val: var string)
{.gcsafe, raises: [IOError, JsonReaderError].} =
mixin flavorSkipNullFields
type
Reader = typeof r
Flavor = Reader.Flavor
const skipNullFields = flavorSkipNullFields(Flavor)
case r.tokKind
of JsonValueKind.String:
escapeJson(r.parseString(), val)
@ -298,7 +304,7 @@ proc parseAsString*(r: var JsonReader, val: var string)
r.lex.scanNumber(val)
r.checkError
of JsonValueKind.Object:
parseObjectImpl(r.lex):
parseObjectImpl(r.lex, skipNullFields):
# initial action
val.add '{'
do: # closing action
@ -355,7 +361,7 @@ proc parseValue*(r: var JsonReader, val: var JsonValueRef)
{.gcsafe, raises: [IOError, JsonReaderError].} =
r.lex.scanValue(val)
r.checkError
template parseArray*(r: var JsonReader; body: untyped) =
if r.tokKind != JsonValueKind.Array:
r.raiseParserError(errBracketLeExpected)
@ -375,9 +381,15 @@ template parseArray*(r: var JsonReader; idx: untyped; body: untyped) =
do: r.raiseParserError() # error action
template parseObject*(r: var JsonReader, key: untyped, body: untyped) =
mixin flavorSkipNullFields
type
Reader = typeof r
Flavor = Reader.Flavor
const skipNullFields = flavorSkipNullFields(typeof Flavor)
if r.tokKind != JsonValueKind.Object:
r.raiseParserError(errCurlyLeExpected)
parseObjectImpl(r.lex): discard # initial action
parseObjectImpl(r.lex, skipNullFields): discard # initial action
do: discard # closing action
do: discard # comma action
do: # key action
@ -388,9 +400,15 @@ template parseObject*(r: var JsonReader, key: untyped, body: untyped) =
r.raiseParserError()
template parseObjectCustomKey*(r: var JsonReader, keyAction: untyped, body: untyped) =
mixin flavorSkipNullFields
type
Reader = typeof r
Flavor = Reader.Flavor
const skipNullFields = flavorSkipNullFields(Flavor)
if r.tokKind != JsonValueKind.Object:
r.raiseParserError(errCurlyLeExpected)
parseObjectImpl(r.lex): discard # initial action
parseObjectImpl(r.lex, skipNullFields): discard # initial action
do: discard # closing action
do: discard # comma action
do: # key action
@ -414,6 +432,12 @@ proc readJsonNodeField(r: var JsonReader, field: var JsonNode)
field = r.parseJsonNode()
proc parseJsonNode(r: var JsonReader): JsonNode =
mixin flavorSkipNullFields
type
Reader = typeof r
Flavor = Reader.Flavor
const skipNullFields = flavorSkipNullFields(Flavor)
case r.tokKind
of JsonValueKind.String:
result = JsonNode(kind: JString, str: r.parseString())
@ -428,7 +452,7 @@ proc parseJsonNode(r: var JsonReader): JsonNode =
r.toInt(val, typeof(result.num), JsonReaderFlag.portableInt in r.lex.flags))
of JsonValueKind.Object:
result = JsonNode(kind: JObject)
parseObjectImpl(r.lex): discard # initial action
parseObjectImpl(r.lex, skipNullFields): discard # initial action
do: discard # closing action
do: discard # comma action
do: # key action

View File

@ -51,6 +51,9 @@ type
three: JsonNumber[string]
four: JsonValueRef[uint64]
ListOnly = object
list: JsonString
Container.useDefaultSerializationIn StringyJson
createJsonFlavor OptJson
@ -71,7 +74,20 @@ const
}
}
}
"""
jsonTextWithNullFields = """
{
"list": null
}
"""
createJsonFlavor NullyFields,
skipNullFields = true,
requireAllFields = false
Container.useDefaultSerializationIn NullyFields
ListOnly.useDefaultSerializationIn NullyFields
suite "Test JsonFlavor":
test "basic test":
@ -104,3 +120,15 @@ suite "Test JsonFlavor":
check:
ww == vv
xx == """{"two":-789.0009e-19,"three":999.776000e33,"four":{"apple":[1,true,"three"],"banana":{"chip":123,"z":null,"v":false}}}"""
test "object with null fields":
expect JsonReaderError:
let x = Json.decode(jsonTextWithNullFields, Container)
discard x
let x = NullyFields.decode(jsonTextWithNullFields, Container)
check x.list.len == 0
# field should not processed at all
let y = NullyFields.decode(jsonTextWithNullFields, ListOnly)
check y.list.string.len == 0