From b04435fb882b2d53cd6d315ad37fe45e2fdcc8de Mon Sep 17 00:00:00 2001 From: Eric <5089238+emizzle@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:08:14 +1100 Subject: [PATCH] Change parseJson to JsonNode.parse (#4) * Change parseJson to JsonNode.parse Exporting `parseJson` causes symbol clashes in downstream repos that import std/json, so changing the signature completely avoid this clash. * Fix usages of parseJson, update README --- README.md | 43 ++++++++++++++++++++++++---------- serde/json/deserializer.nim | 8 +++---- serde/json/parser.nim | 5 ++-- tests/json/testDeserialize.nim | 32 ++++++++++++++++--------- 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 0882d3f..128a946 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,10 @@ OptOut Strict ``` -Modes can be set in the `{.serialize.}` and/or `{.deserialize.}` pragmas on type definitions. Each mode has a different meaning depending on if the type is being serialized or deserialized. Modes can be set by setting `mode` in the `serialize` or `deserialize` pragma annotation, eg: +Modes can be set in the `{.serialize.}` and/or `{.deserialize.}` pragmas on type +definitions. Each mode has a different meaning depending on if the type is being +serialized or deserialized. Modes can be set by setting `mode` in the `serialize` or +`deserialize` pragma annotation, eg: ```nim type MyType {.serialize(mode=Strict).} = object @@ -126,7 +129,11 @@ type MyType {.serialize(mode=Strict).} = object ## Default modes -`nim-serde` will de/serialize types if they are not annotated with `serialize` or `deserialize`, but will assume a default mode. By default, with no pragmas specified, `serde` will always serialize in `OptIn` mode, meaning any fields to b Additionally, if the types are annotated, but a mode is not specified, `serde` will assume a (possibly different) default mode. +`nim-serde` will de/serialize types if they are not annotated with `serialize` or +`deserialize`, but will assume a default mode. By default, with no pragmas specified, +`serde` will always serialize in `OptIn` mode, meaning any fields to b Additionally, if +the types are annotated, but a mode is not specified, `serde` will assume a (possibly +different) default mode. ```nim # Type is not annotated @@ -152,7 +159,8 @@ type MyObj {.serialize, deserialize.} = object | Default (pragma, but no mode) | `OptOut` | `OptOut` | ## Serde field options -Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties can be set on these pragmas, determining de/serialization behavior. +Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties +can be set on these pragmas, determining de/serialization behavior. For example, @@ -192,7 +200,9 @@ assert !Person.fromJson(createResponse) == Person(id: 1) ``` ### `key` -Specifying a `key`, will alias the field name. When seriazlizing, json will be written with `key` instead of the field name. When deserializing, the json must contain `key` for the field to be deserialized. +Specifying a `key`, will alias the field name. When seriazlizing, json will be written +with `key` instead of the field name. When deserializing, the json must contain `key` +for the field to be deserialized. ### `ignore` Specifying `ignore`, will prevent de/serialization on the field. @@ -207,7 +217,8 @@ Specifying `ignore`, will prevent de/serialization on the field. ## Deserialization -`serde` deserializes using `fromJson`, and in all instances returns `Result[T, CatchableError]`, where `T` is the type being deserialized. For example: +`serde` deserializes using `fromJson`, and in all instances returns `Result[T, +CatchableError]`, where `T` is the type being deserialized. For example: ```nim type MyType = object @@ -266,7 +277,8 @@ func fromJson(_: type Address, json: JsonNode): ?!Address = ## Serializing to string (`toJson`) -`toJson` is a shortcut for serializing an object into its serialized string representation: +`toJson` is a shortcut for serializing an object into its serialized string +representation: ```nim import pkg/serde/json @@ -289,9 +301,11 @@ return RestApiResponse.response(availability.toJson, ## `std/json` drop-in replacment -`nim-serde` can be used as a drop-in replacement for the [standard library's `json` module](https://nim-lang.org/docs/json.html), with a few notable improvements. +`nim-serde` can be used as a drop-in replacement for the [standard library's `json` +module](https://nim-lang.org/docs/json.html), with a few notable improvements. -Instead of importing `std/json` into your application, `pkg/serde/json` can be imported instead: +Instead of importing `std/json` into your application, `pkg/serde/json` can be imported +instead: ```diff - import std/json @@ -316,7 +330,8 @@ expected["hello"] = newJString("world") assert %*{"hello": "world"} == expected ``` -As well, serialization of types can be overridden, and serialization of custom types can be introduced. Here, we are overriding the serialization of `int`: +As well, serialization of types can be overridden, and serialization of custom types can +be introduced. Here, we are overriding the serialization of `int`: ```nim import pkg/serde/json @@ -329,7 +344,8 @@ assert 1.toJson == "2" ## `parseJson` and exception tracking -Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception tracking breaks, eg +Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception +tracking breaks, eg ```nim @@ -352,7 +368,10 @@ proc parseMe(me: string): JsonNode = assert """{"hello":"world"}""".parseMe == %* { "hello": "world" } ``` -This is due to `std/json`'s `parseJson` incorrectly raising `Exception`. This can be worked around by instead importing `serde` and calling its `parseJson`. Note that `serde`'s `parseJson` returns a `Result[JsonNode, CatchableError]` instead of just a plain `JsonNode` object: +This is due to `std/json`'s `parseJson` incorrectly raising `Exception`. This can be +worked around by instead importing `serde` and calling its `JsonNode.parse` routine. +Note that `serde`'s `JsonNode.parse` returns a `Result[JsonNode, CatchableError]` +instead of just a plain `JsonNode` object as in `std/json`'s `parseJson`: ```nim import pkg/serde/json @@ -363,7 +382,7 @@ type MyAppError = object of CatchableError proc parseMe(me: string): JsonNode {.raises: [MyAppError].} = - without parsed =? me.parseJson, error: + without parsed =? JsonNode.parse(me), error: raise newException(MyAppError, error.msg) parsed diff --git a/serde/json/deserializer.nim b/serde/json/deserializer.nim index 4d75902..a4069a8 100644 --- a/serde/json/deserializer.nim +++ b/serde/json/deserializer.nim @@ -229,8 +229,8 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T = success(res) -proc fromJson*(_: type JsonNode, jsn: string): ?!JsonNode = - return parser.parseJson(jsn) +proc fromJson*(_: type JsonNode, json: string): ?!JsonNode = + return JsonNode.parse(json) proc fromJson*[T: ref object or object](_: type T, bytes: openArray[byte]): ?!T = let json = string.fromBytes(bytes) @@ -238,6 +238,6 @@ proc fromJson*[T: ref object or object](_: type T, bytes: openArray[byte]): ?!T echo "typeof json after parse: ", typeof json T.fromJson(json) -proc fromJson*[T: ref object or object](_: type T, jsn: string): ?!T = - let jsn = ?parser.parseJson(jsn) # full qualification required in-module only +proc fromJson*[T: ref object or object](_: type T, json: string): ?!T = + let jsn = ?JsonNode.parse(json) # full qualification required in-module only T.fromJson(jsn) diff --git a/serde/json/parser.nim b/serde/json/parser.nim index caa855c..ee09c23 100644 --- a/serde/json/parser.nim +++ b/serde/json/parser.nim @@ -6,8 +6,9 @@ import ./types {.push raises: [].} -proc parseJson*(json: string): ?!JsonNode = - ## fix for nim raising Exception +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 except Exception as e: diff --git a/tests/json/testDeserialize.nim b/tests/json/testDeserialize.nim index c0e7ef3..2a689e1 100644 --- a/tests/json/testDeserialize.nim +++ b/tests/json/testDeserialize.nim @@ -49,7 +49,7 @@ suite "json serialization - deserialize": test "deserializes array to sequence": let expected = @[1, 2, 3] - let json = !"[1,2,3]".parseJson + let json = !JsonNode.parse("[1,2,3]") check !seq[int].fromJson(json) == expected test "deserializes uints int.high or smaller": @@ -71,11 +71,13 @@ suite "json serialization - deserialize": let expected = MyObj(mystring: "abc", myint: 123, myoption: some true) let json = - !"""{ + !JsonNode.parse( + """{ "mystring": "abc", "myint": 123, "myoption": true - }""".parseJson + }""" + ) check !MyObj.fromJson(json) == expected @@ -87,10 +89,12 @@ suite "json serialization - deserialize": let expected = MyObj(mystring: "abc", mybool: true) let json = - !"""{ + !JsonNode.parse( + """{ "mystring": "abc", "mybool": true - }""".parseJson + }""" + ) check !MyObj.fromJson(json) == expected @@ -102,11 +106,13 @@ suite "json serialization - deserialize": let expected = MyObj(mystring: "abc", mybool: true) let json = - !"""{ + !JsonNode.parse( + """{ "mystring": "abc", "mybool": true, "extra": "extra" - }""".parseJson + }""" + ) check !MyObj.fromJson(json) == expected test "deserializes objects with less fields": @@ -117,9 +123,11 @@ suite "json serialization - deserialize": let expected = MyObj(mystring: "abc", mybool: false) let json = - !"""{ + !JsonNode.parse( + """{ "mystring": "abc" - }""".parseJson + }""" + ) check !MyObj.fromJson(json) == expected test "deserializes ref objects": @@ -130,10 +138,12 @@ suite "json serialization - deserialize": let expected = MyRef(mystring: "abc", myint: 1) let json = - !"""{ + !JsonNode.parse( + """{ "mystring": "abc", "myint": 1 - }""".parseJson + }""" + ) let deserialized = !MyRef.fromJson(json) check deserialized.mystring == expected.mystring