Add flavor feature: reader will skip null fields
This commit is contained in:
parent
9c74b885ea
commit
0db6307eb3
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 '{'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue