feat: improve deserialization from string (#23)
Improves how types handle deserialization from string, including Option[T], seq[T], and Option[seq[T]] Empty and null strings are no longer deserialized to 0, instead an error Result is returned If an error occurs when parsing json, a JsonParseError is returned.
This commit is contained in:
parent
9dd16685d1
commit
10271bd494
|
@ -79,6 +79,12 @@ proc fromJson*[T: SomeInteger](_: type T, json: JsonNode): ?!T =
|
|||
expectJsonKind(T, {JInt, JString}, json)
|
||||
case json.kind
|
||||
of JString:
|
||||
if json.isNullString:
|
||||
let err = newSerdeError("Cannot deserialize 'null' into type " & $T)
|
||||
return failure(err)
|
||||
elif json.isEmptyString:
|
||||
return success T(0)
|
||||
|
||||
without x =? parseBiggestUInt(json.str).catch, error:
|
||||
return failure newSerdeError(error.msg)
|
||||
return success cast[T](x)
|
||||
|
@ -125,16 +131,15 @@ 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, JInt, JNull}, json)
|
||||
expectJsonKind(T, {JString, JInt}, 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 err = newSerdeError("Cannot deserialize 'null' into type " & $T)
|
||||
return failure(err)
|
||||
|
||||
let jsonStr = json.getStr
|
||||
let prefix =
|
||||
|
@ -253,9 +258,62 @@ proc fromJson*[T: ref object or object](_: type T, bytes: openArray[byte]): ?!T
|
|||
T.fromJson(json)
|
||||
|
||||
proc fromJson*[T: ref object or object](_: type T, json: string): ?!T =
|
||||
echo "here1, T: ", T
|
||||
when T is Option:
|
||||
echo " we have an option!"
|
||||
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
|
||||
T.fromJson(jsn)
|
||||
|
||||
proc fromJson*[T: enum](_: type T, json: string): ?!T =
|
||||
T.fromJson(newJString(json))
|
||||
|
||||
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool](
|
||||
_: type T, json: string
|
||||
): ?!T =
|
||||
if json == "" or json == "null":
|
||||
let err = newSerdeError("Cannot deserialize '' or 'null' into type " & $T)
|
||||
failure err
|
||||
else:
|
||||
let jsn = ?JsonNode.parse(json)
|
||||
T.fromJson(jsn)
|
||||
|
||||
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
|
||||
_: type Option[T], json: string
|
||||
): ?!Option[T] =
|
||||
if json == "" or json == "null":
|
||||
success T.none
|
||||
else:
|
||||
when T is enum:
|
||||
let jsn = newJString(json)
|
||||
else:
|
||||
let jsn = ?JsonNode.parse(json)
|
||||
Option[T].fromJson(jsn)
|
||||
|
||||
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
|
||||
_: type seq[T], json: string
|
||||
): ?!seq[T] =
|
||||
if json == "" or json == "null":
|
||||
success newSeq[T]()
|
||||
else:
|
||||
if T is enum:
|
||||
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome")
|
||||
return failure err
|
||||
|
||||
let jsn = ?JsonNode.parse(json)
|
||||
seq[T].fromJson(jsn)
|
||||
|
||||
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
|
||||
_: type ?seq[T], json: string
|
||||
): ?!Option[seq[T]] =
|
||||
if json == "" or json == "null":
|
||||
success seq[T].none
|
||||
else:
|
||||
if T is enum:
|
||||
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome")
|
||||
return failure err
|
||||
let jsn = ?JsonNode.parse(json)
|
||||
Option[seq[T]].fromJson(jsn)
|
||||
|
||||
proc fromJson*[T: ref object or object](_: type seq[T], json: string): ?!seq[T] =
|
||||
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
|
||||
seq[T].fromJson(jsn)
|
||||
|
|
|
@ -2,6 +2,7 @@ import std/json as stdjson
|
|||
|
||||
import pkg/questionable/results
|
||||
|
||||
import ./errors
|
||||
import ./types
|
||||
|
||||
{.push raises: [].}
|
||||
|
@ -10,6 +11,8 @@ proc parse*(_: type JsonNode, json: string): ?!JsonNode =
|
|||
# Used as a replacement for `std/json.parseJson`. Will not raise Exception like in the
|
||||
# standard library
|
||||
try:
|
||||
return stdjson.parseJson(json).catch
|
||||
without val =? stdjson.parseJson(json).catch, error:
|
||||
return failure error.mapErrTo(JsonParseError)
|
||||
return success val
|
||||
except Exception as e:
|
||||
return failure newException(JsonParseError, e.msg, e)
|
||||
|
|
|
@ -20,6 +20,20 @@ suite "json - deserialize std types":
|
|||
let json = newJInt(1)
|
||||
check (!fromJson(?int, json) == some 1)
|
||||
|
||||
test "deserializes Option[T] when has a string value":
|
||||
check (!fromJson(?int, "1") == some 1)
|
||||
|
||||
test "deserializes Option[T] from empty string":
|
||||
check (!fromJson(?int, "") == int.none)
|
||||
|
||||
test "deserializes Option[T] from empty string":
|
||||
check (!fromJson(?int, "") == int.none)
|
||||
|
||||
test "cannot deserialize T from null string":
|
||||
let res = fromJson(int, "null")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Cannot deserialize '' or 'null' into type int"
|
||||
|
||||
test "deserializes Option[T] when doesn't have a value":
|
||||
let json = newJNull()
|
||||
check !fromJson(?int, json) == none int
|
||||
|
@ -28,6 +42,19 @@ suite "json - deserialize std types":
|
|||
let json = newJFloat(1.234)
|
||||
check !float.fromJson(json) == 1.234
|
||||
|
||||
test "deserializes float from string":
|
||||
check !float.fromJson("1.234") == 1.234
|
||||
|
||||
test "cannot deserialize float from empty string":
|
||||
let res = float.fromJson("")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Cannot deserialize '' or 'null' into type float"
|
||||
|
||||
test "cannot deserialize float from null string":
|
||||
let res = float.fromJson("null")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Cannot deserialize '' or 'null' into type float"
|
||||
|
||||
test "deserializes Inf float":
|
||||
let json = newJString("inf")
|
||||
check !float.fromJson(json) == Inf
|
||||
|
@ -54,3 +81,137 @@ suite "json - deserialize std types":
|
|||
let largeUInt: uint = uint(int.high) + 1'u
|
||||
let json = newJString($BiggestUInt(largeUInt))
|
||||
check !uint.fromJson(json) == largeUInt
|
||||
|
||||
test "deserializes bool from JBool":
|
||||
let json = newJBool(true)
|
||||
check !bool.fromJson(json)
|
||||
|
||||
test "deserializes bool from string":
|
||||
check !bool.fromJson("true")
|
||||
|
||||
test "cannot deserialize bool from empty string":
|
||||
let res = bool.fromJson("")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Cannot deserialize '' or 'null' into type bool"
|
||||
|
||||
test "cannot deserialize bool from null string":
|
||||
let res = bool.fromJson("null")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Cannot deserialize '' or 'null' into type bool"
|
||||
|
||||
test "deserializes ?bool from string":
|
||||
check Option[bool].fromJson("true") == success true.some
|
||||
|
||||
test "deserializes ?bool from empty string":
|
||||
check !Option[bool].fromJson("") == bool.none
|
||||
|
||||
test "deserializes ?bool from null string":
|
||||
check !Option[bool].fromJson("null") == bool.none
|
||||
|
||||
test "deserializes seq[bool] from JArray":
|
||||
let json = newJArray()
|
||||
json.add(newJBool(true))
|
||||
json.add(newJBool(false))
|
||||
check !seq[bool].fromJson(json) == @[true, false]
|
||||
|
||||
test "deserializes seq[bool] from string":
|
||||
check !seq[bool].fromJson("[true, false]") == @[true, false]
|
||||
|
||||
test "deserializes seq[bool] from empty string":
|
||||
check !seq[bool].fromJson("") == newSeq[bool]()
|
||||
|
||||
test "deserializes seq[bool] from null string":
|
||||
check !seq[bool].fromJson("null") == newSeq[bool]()
|
||||
|
||||
test "cannot deserialize seq[bool] from unknown string":
|
||||
let res = seq[bool].fromJson("blah")
|
||||
check res.error of JsonParseError
|
||||
check res.error.msg == "input(1, 4) Error: { expected"
|
||||
|
||||
test "deserializes ?seq[bool] from string":
|
||||
check Option[seq[bool]].fromJson("[true, false]") == success @[true, false].some
|
||||
|
||||
test "deserializes ?seq[bool] from empty string":
|
||||
check !Option[seq[bool]].fromJson("") == seq[bool].none
|
||||
|
||||
test "deserializes ?seq[bool] from null string":
|
||||
check !Option[seq[bool]].fromJson("null") == seq[bool].none
|
||||
|
||||
test "deserializes enum from JString":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
let json = newJString("one")
|
||||
check !MyEnum.fromJson(json) == MyEnum.one
|
||||
|
||||
test "deserializes enum from string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
check !MyEnum.fromJson("one") == MyEnum.one
|
||||
|
||||
test "cannot deserialize enum from empty string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
let res = MyEnum.fromJson("")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Invalid enum value: "
|
||||
|
||||
test "cannot deserialize enum from null string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
let res = MyEnum.fromJson("null")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Invalid enum value: null"
|
||||
|
||||
test "deserializes ?enum from string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
check Option[MyEnum].fromJson("one") == success MyEnum.one.some
|
||||
|
||||
test "deserializes ?enum from empty string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
check !Option[MyEnum].fromJson("") == MyEnum.none
|
||||
|
||||
test "deserializes ?enum from null string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
check !Option[MyEnum].fromJson("null") == MyEnum.none
|
||||
|
||||
test "deserializes seq[enum] from string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
two
|
||||
|
||||
let res = seq[MyEnum].fromJson("[one,two]")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg ==
|
||||
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
|
||||
|
||||
test "deserializes ?seq[enum] from string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
two
|
||||
|
||||
let res = Option[seq[MyEnum]].fromJson("[one,two]")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg ==
|
||||
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
|
||||
|
||||
test "deserializes ?seq[MyEnum] from empty string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
check !Option[seq[MyEnum]].fromJson("") == seq[MyEnum].none
|
||||
|
||||
test "deserializes ?seq[MyEnum] from null string":
|
||||
type MyEnum = enum
|
||||
one
|
||||
|
||||
check !Option[seq[MyEnum]].fromJson("null") == seq[MyEnum].none
|
||||
|
|
|
@ -14,10 +14,15 @@ suite "json - deserialize stint":
|
|||
check !UInt256.fromJson("") == 0.u256
|
||||
|
||||
test "deserializes UInt256 from null string":
|
||||
check !UInt256.fromJson("null") == 0.u256
|
||||
let res = UInt256.fromJson("null")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Cannot deserialize 'null' into type UInt256"
|
||||
|
||||
test "deserializes UInt256 from JNull":
|
||||
check !UInt256.fromJson(newJNull()) == 0.u256
|
||||
let res = UInt256.fromJson(newJNull())
|
||||
check res.error of UnexpectedKindError
|
||||
check res.error.msg ==
|
||||
"deserialization to UInt256 failed: expected {JInt, JString} but got JNull"
|
||||
|
||||
test "deserializes ?UInt256 from an empty JString":
|
||||
let json = newJString("")
|
||||
|
@ -39,10 +44,15 @@ suite "json - deserialize stint":
|
|||
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]
|
||||
let res = seq[UInt256].fromJson("[1,2,null]")
|
||||
check res.error of UnexpectedKindError
|
||||
check res.error.msg ==
|
||||
"deserialization to UInt256 failed: expected {JInt, JString} but got JNull"
|
||||
|
||||
test "deserializes seq[UInt256] from string with null string item":
|
||||
check seq[UInt256].fromJson("[1,2,\"null\"]") == success @[1.u256, 2.u256, 0.u256]
|
||||
let res = seq[UInt256].fromJson("[1,2,\"null\"]")
|
||||
check res.error of SerdeError
|
||||
check res.error.msg == "Cannot deserialize 'null' into type UInt256"
|
||||
|
||||
test "deserializes seq[?UInt256] from string":
|
||||
check seq[?UInt256].fromJson("[1,2,3]") ==
|
||||
|
|
Loading…
Reference in New Issue