From 6d2fc9406a56a05dec5be196a772091f1264db94 Mon Sep 17 00:00:00 2001 From: Eric <5089238+emizzle@users.noreply.github.com> Date: Thu, 16 May 2024 15:09:01 +1000 Subject: [PATCH] feat: improve stint parsing (#22) - change empty string value to none when optional - handle null, "null", JNull, seq[stint], seq[?string] --- serde/json/deserializer.nim | 43 ++++++++++++++++++----------- serde/json/helpers.nim | 7 +++++ tests/json/deserialize/objects.nim | 42 ++++++++++++++++++++++++++++ tests/json/deserialize/stint.nim | 44 ++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 18 deletions(-) create mode 100644 serde/json/helpers.nim diff --git a/serde/json/deserializer.nim b/serde/json/deserializer.nim index 3624dd5..dee909a 100644 --- a/serde/json/deserializer.nim +++ b/serde/json/deserializer.nim @@ -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) diff --git a/serde/json/helpers.nim b/serde/json/helpers.nim new file mode 100644 index 0000000..d21ea62 --- /dev/null +++ b/serde/json/helpers.nim @@ -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" diff --git a/tests/json/deserialize/objects.nim b/tests/json/deserialize/objects.nim index 0ae8cbe..f295f91 100644 --- a/tests/json/deserialize/objects.nim +++ b/tests/json/deserialize/objects.nim @@ -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 diff --git a/tests/json/deserialize/stint.nim b/tests/json/deserialize/stint.nim index b7428dd..3546f0c 100644 --- a/tests/json/deserialize/stint.nim +++ b/tests/json/deserialize/stint.nim @@ -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")