feat: improve stint parsing (#22)

- change empty string value to none when optional
- handle null, "null", JNull, seq[stint], seq[?string]
This commit is contained in:
Eric 2024-05-16 15:09:01 +10:00 committed by GitHub
parent d84641333a
commit 6d2fc9406a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 118 additions and 18 deletions

View File

@ -16,6 +16,7 @@ import ./errors
import ./stdjson
import ./pragmas
import ./types
import ./helpers
export parser
export chronicles except toJson
@ -124,25 +125,35 @@ proc fromJson*[T: distinct](_: type T, json: JsonNode): ?!T =
success T(?T.distinctBase.fromJson(json))
proc fromJson*(T: typedesc[StUint or StInt], json: JsonNode): ?!T =
expectJsonKind(T, JString, json)
let jsonStr = json.getStr
let prefix =
if jsonStr.len >= 2:
jsonStr[0 .. 1].toLowerAscii
expectJsonKind(T, {JString, JInt, JNull}, json)
case json.kind
of JNull: # return 0, optional values are handled up the call stack
return catch parse("0", T)
of JInt:
return catch parse($json, T)
else: # JString (only other kind allowed)
if json.isNullString:
return catch parse("0", T)
let jsonStr = json.getStr
let prefix =
if jsonStr.len >= 2:
jsonStr[0 .. 1].toLowerAscii
else:
jsonStr
case prefix
of "0x":
catch parse(jsonStr, T, 16)
of "0o":
catch parse(jsonStr, T, 8)
of "0b":
catch parse(jsonStr, T, 2)
else:
jsonStr
case prefix
of "0x":
catch parse(jsonStr, T, 16)
of "0o":
catch parse(jsonStr, T, 8)
of "0b":
catch parse(jsonStr, T, 2)
else:
catch parse(jsonStr, T)
catch parse(jsonStr, T)
proc fromJson*[T](_: type Option[T], json: JsonNode): ?!Option[T] =
if json.isNil or json.kind == JNull:
if json.isNil or json.kind == JNull or json.isEmptyString or json.isNullString:
return success(none T)
without val =? T.fromJson(json), error:
return failure(error)

7
serde/json/helpers.nim Normal file
View File

@ -0,0 +1,7 @@
import std/json
func isEmptyString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == ""
func isNullString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == "null"

View File

@ -3,6 +3,7 @@ import pkg/serde
import pkg/questionable
import pkg/questionable/results
import pkg/stew/byteutils
import pkg/stint
suite "json - deserialize objects":
test "can deserialize json objects":
@ -165,3 +166,44 @@ suite "json - deserialize objects from string":
}"""
check !(Option[MyObj].fromJson(myObjJson)) == expected
test "deserializes object with UInt256 from string":
type MyObj = object
mystring: string
myu256: UInt256
let expected = MyObj(mystring: "abc", myu256: 1.u256)
let myObjJson =
"""{
"mystring": "abc",
"myu256": 1
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes object with stringified UInt256 from string":
type MyObj = object
mystring: string
myu256: UInt256
let expected = MyObj(mystring: "abc", myu256: 1.u256)
let myObjJson =
"""{
"mystring": "abc",
"myu256": "1"
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes object with ?UInt256 from string":
type MyObj = object
mystring: string
myu256: ?UInt256
let expected = MyObj(mystring: "abc", myu256: UInt256.none)
let myObjJson =
"""{
"mystring": "abc",
}"""
check !MyObj.fromJson(myObjJson) == expected

View File

@ -13,12 +13,52 @@ suite "json - deserialize stint":
test "deserializes UInt256 from an empty string":
check !UInt256.fromJson("") == 0.u256
test "deserializes UInt256 from null string":
check !UInt256.fromJson("null") == 0.u256
test "deserializes UInt256 from JNull":
check !UInt256.fromJson(newJNull()) == 0.u256
test "deserializes ?UInt256 from an empty JString":
let json = newJString("")
check !Option[UInt256].fromJson(json) == 0.u256.some
check !Option[UInt256].fromJson(json) == UInt256.none
test "deserializes ?UInt256 from an empty string":
check !Option[UInt256].fromJson("") == 0.u256.some
check !Option[UInt256].fromJson("") == UInt256.none
test "deserializes ?UInt256 from null string":
check !Option[UInt256].fromJson("null") == UInt256.none
test "deserializes ?UInt256 from JNull":
check !Option[UInt256].fromJson(newJNull()) == UInt256.none
test "deserializes seq[UInt256] from string":
check seq[UInt256].fromJson("[1,2,3]") == success @[1.u256, 2.u256, 3.u256]
test "deserializes seq[UInt256] from string with empty string item":
check seq[UInt256].fromJson("[1,2,\"\"]") == success @[1.u256, 2.u256, 0.u256]
test "deserializes seq[UInt256] from string with null item":
check seq[UInt256].fromJson("[1,2,null]") == success @[1.u256, 2.u256, 0.u256]
test "deserializes seq[UInt256] from string with null string item":
check seq[UInt256].fromJson("[1,2,\"null\"]") == success @[1.u256, 2.u256, 0.u256]
test "deserializes seq[?UInt256] from string":
check seq[?UInt256].fromJson("[1,2,3]") ==
success @[1.u256.some, 2.u256.some, 3.u256.some]
test "deserializes seq[?UInt256] from string with empty string item":
check seq[?UInt256].fromJson("[1,2,\"\"]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes seq[?UInt256] from string with null item":
check seq[?UInt256].fromJson("[1,2,null]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes seq[?UInt256] from string with null string item":
check seq[?UInt256].fromJson("[1,2,\"null\"]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes UInt256 from JString with no prefix":
let json = newJString("1")