2023-12-13 09:07:57 +00:00
|
|
|
# json-serialization
|
|
|
|
# Copyright (c) 2019-2023 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
# at your option.
|
|
|
|
# This file may not be copied, modified, or distributed except according to
|
|
|
|
# those terms.
|
|
|
|
|
2021-03-18 11:01:06 +00:00
|
|
|
import
|
2023-12-18 04:05:12 +00:00
|
|
|
std/[strutils, options],
|
|
|
|
unittest2,
|
|
|
|
results,
|
2021-03-18 11:01:06 +00:00
|
|
|
serialization,
|
2023-12-18 04:05:12 +00:00
|
|
|
../json_serialization/stew/results,
|
|
|
|
../json_serialization/std/options,
|
2021-03-18 11:01:06 +00:00
|
|
|
../json_serialization
|
|
|
|
|
2023-12-18 04:05:12 +00:00
|
|
|
createJsonFlavor StringyJson
|
2021-03-18 11:01:06 +00:00
|
|
|
|
2023-08-19 11:47:32 +00:00
|
|
|
proc writeValue*(
|
|
|
|
w: var JsonWriter[StringyJson], val: SomeInteger) {.raises: [IOError].} =
|
2021-03-18 11:01:06 +00:00
|
|
|
writeValue(w, $val)
|
|
|
|
|
|
|
|
proc readValue*(r: var JsonReader[StringyJson], v: var SomeSignedInt) =
|
|
|
|
try:
|
|
|
|
v = type(v) parseBiggestInt readValue(r, string)
|
|
|
|
except ValueError as err:
|
2023-12-18 04:05:12 +00:00
|
|
|
r.raiseUnexpectedValue("A signed integer encoded as string " & err.msg)
|
2021-03-18 11:01:06 +00:00
|
|
|
|
|
|
|
proc readValue*(r: var JsonReader[StringyJson], v: var SomeUnsignedInt) =
|
|
|
|
try:
|
|
|
|
v = type(v) parseBiggestUInt readValue(r, string)
|
|
|
|
except ValueError as err:
|
2023-12-18 04:05:12 +00:00
|
|
|
r.raiseUnexpectedValue("An unsigned integer encoded as string " & err.msg)
|
2021-03-18 11:01:06 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
Container = object
|
|
|
|
name: string
|
|
|
|
x: int
|
|
|
|
y: uint64
|
|
|
|
list: seq[int64]
|
|
|
|
|
2023-12-18 04:05:12 +00:00
|
|
|
OptionalFields = object
|
|
|
|
one: Opt[string]
|
|
|
|
two: Option[int]
|
2021-03-18 11:01:06 +00:00
|
|
|
|
2023-12-27 04:50:26 +00:00
|
|
|
SpecialTypes = object
|
|
|
|
one: JsonVoid
|
|
|
|
two: JsonNumber[uint64]
|
|
|
|
three: JsonNumber[string]
|
|
|
|
four: JsonValueRef[uint64]
|
|
|
|
|
2024-01-15 03:28:23 +00:00
|
|
|
ListOnly = object
|
|
|
|
list: JsonString
|
|
|
|
|
2023-12-18 04:05:12 +00:00
|
|
|
Container.useDefaultSerializationIn StringyJson
|
2021-03-18 11:01:06 +00:00
|
|
|
|
2023-12-18 04:05:12 +00:00
|
|
|
createJsonFlavor OptJson
|
|
|
|
OptionalFields.useDefaultSerializationIn OptJson
|
|
|
|
|
2023-12-27 04:50:26 +00:00
|
|
|
const
|
|
|
|
jsonText = """
|
|
|
|
{
|
|
|
|
"one": "this text will gone",
|
|
|
|
"two": -789.0009E-19,
|
|
|
|
"three": 999.776000E+33,
|
|
|
|
"four" : {
|
|
|
|
"apple": [1, true, "three"],
|
|
|
|
"banana": {
|
|
|
|
"chip": 123,
|
|
|
|
"z": null,
|
|
|
|
"v": false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-01-15 03:28:23 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
jsonTextWithNullFields = """
|
|
|
|
{
|
|
|
|
"list": null
|
|
|
|
}
|
2023-12-27 04:50:26 +00:00
|
|
|
"""
|
|
|
|
|
2024-01-15 03:28:23 +00:00
|
|
|
createJsonFlavor NullyFields,
|
|
|
|
skipNullFields = true,
|
|
|
|
requireAllFields = false
|
|
|
|
|
|
|
|
Container.useDefaultSerializationIn NullyFields
|
|
|
|
ListOnly.useDefaultSerializationIn NullyFields
|
|
|
|
|
2023-12-18 04:05:12 +00:00
|
|
|
suite "Test JsonFlavor":
|
|
|
|
test "basic test":
|
|
|
|
let c = Container(name: "c", x: -10, y: 20, list: @[1'i64, 2, 25])
|
|
|
|
let encoded = StringyJson.encode(c)
|
|
|
|
check encoded == """{"name":"c","x":"-10","y":"20","list":["1","2","25"]}"""
|
|
|
|
|
|
|
|
let decoded = StringyJson.decode(encoded, Container)
|
|
|
|
check decoded == Container(name: "c", x: -10, y: 20, list: @[1, 2, 25])
|
|
|
|
|
|
|
|
test "optional fields":
|
|
|
|
let a = OptionalFields(one: Opt.some("hello"))
|
|
|
|
let b = OptionalFields(two: some(567))
|
|
|
|
let c = OptionalFields(one: Opt.some("burn"), two: some(333))
|
|
|
|
|
|
|
|
let aa = OptJson.encode(a)
|
|
|
|
check aa == """{"one":"hello"}"""
|
|
|
|
|
|
|
|
let bb = OptJson.encode(b)
|
|
|
|
check bb == """{"two":567}"""
|
|
|
|
|
|
|
|
let cc = OptJson.encode(c)
|
|
|
|
check cc == """{"one":"burn","two":333}"""
|
2023-12-27 04:50:26 +00:00
|
|
|
|
|
|
|
test "Write special types":
|
|
|
|
let vv = Json.decode(jsonText, SpecialTypes)
|
|
|
|
let xx = Json.encode(vv)
|
|
|
|
var ww = Json.decode(xx, SpecialTypes)
|
|
|
|
ww.three.expSign = JsonSign.Pos # the rest of it should identical to vv
|
|
|
|
check:
|
|
|
|
ww == vv
|
|
|
|
xx == """{"two":-789.0009e-19,"three":999.776000e33,"four":{"apple":[1,true,"three"],"banana":{"chip":123,"z":null,"v":false}}}"""
|
2024-01-15 03:28:23 +00:00
|
|
|
|
|
|
|
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
|
2024-07-27 00:27:11 +00:00
|
|
|
|
|
|
|
test "Enum value representation primitives":
|
|
|
|
when NullyFields.flavorEnumRep() == EnumAsString:
|
|
|
|
check true
|
|
|
|
elif NullyFields.flavorEnumRep() == EnumAsNumber:
|
|
|
|
check false
|
|
|
|
elif NullyFields.flavorEnumRep() == EnumAsStringifiedNumber:
|
|
|
|
check false
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsNumber)
|
|
|
|
when NullyFields.flavorEnumRep() == EnumAsString:
|
|
|
|
check false
|
|
|
|
elif NullyFields.flavorEnumRep() == EnumAsNumber:
|
|
|
|
check true
|
|
|
|
elif NullyFields.flavorEnumRep() == EnumAsStringifiedNumber:
|
|
|
|
check false
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsStringifiedNumber)
|
|
|
|
when NullyFields.flavorEnumRep() == EnumAsString:
|
|
|
|
check false
|
|
|
|
elif NullyFields.flavorEnumRep() == EnumAsNumber:
|
|
|
|
check false
|
|
|
|
elif NullyFields.flavorEnumRep() == EnumAsStringifiedNumber:
|
|
|
|
check true
|
|
|
|
|
|
|
|
test "Enum value representation of custom flavor":
|
|
|
|
type
|
|
|
|
ExoticFruits = enum
|
|
|
|
DragonFruit
|
|
|
|
SnakeFruit
|
|
|
|
StarFruit
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsNumber)
|
|
|
|
let u = NullyFields.encode(DragonFruit)
|
|
|
|
check u == "0"
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsString)
|
|
|
|
let v = NullyFields.encode(SnakeFruit)
|
|
|
|
check v == "\"SnakeFruit\""
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsStringifiedNumber)
|
|
|
|
let w = NullyFields.encode(StarFruit)
|
|
|
|
check w == "\"2\""
|
|
|
|
|
|
|
|
test "EnumAsString of custom flavor":
|
|
|
|
type
|
|
|
|
Fruit = enum
|
|
|
|
Banana = "BaNaNa"
|
|
|
|
Apple = "ApplE"
|
|
|
|
JackFruit = "VVV"
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsString)
|
|
|
|
let u = NullyFields.encode(Banana)
|
|
|
|
check u == "\"BaNaNa\""
|
|
|
|
|
|
|
|
let v = NullyFields.encode(Apple)
|
|
|
|
check v == "\"ApplE\""
|
|
|
|
|
|
|
|
let w = NullyFields.encode(JackFruit)
|
|
|
|
check w == "\"VVV\""
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsStringifiedNumber)
|
|
|
|
let x = NullyFields.encode(JackFruit)
|
|
|
|
check x == "\"2\""
|
|
|
|
|
|
|
|
NullyFields.flavorEnumRep(EnumAsNumber)
|
|
|
|
let z = NullyFields.encode(Banana)
|
|
|
|
check z == "0"
|