From 369a7d12467831295c12f5f51aa8c69ec3b3596a Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Mon, 31 May 2021 13:17:52 -0700 Subject: [PATCH] jsonutils.toJson now serializes JsonNode as is by default (#18097) * jsonutils.toJson now serializes JsonNode as is (without deep copy nor treating it as a regular ref object) * JsonNodeMode --- changelog.md | 4 +++- lib/std/jsonutils.nim | 20 ++++++++++++++++---- tests/stdlib/tjsonutils.nim | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index d404b1965..9806a6011 100644 --- a/changelog.md +++ b/changelog.md @@ -69,7 +69,9 @@ previous behavior for a transition time, see PR #17467. - `jsonutils` now serializes/deserializes holey enums as regular enums (via `ord`) instead of as strings. - Use `-d:nimLegacyJsonutilsHoleyEnum` for a transition period. + Use `-d:nimLegacyJsonutilsHoleyEnum` for a transition period. `toJson` now serializes `JsonNode` + as is via reference (without a deep copy) instead of treating `JsonNode` as a regular ref object, + this can be customized via `jsonNodeMode`. - `json` and `jsonutils` now serialize NaN, Inf, -Inf as strings, so that `%[NaN, -Inf]` is the string `["nan","-inf"]` instead of `[nan,-inf]` which was invalid json. diff --git a/lib/std/jsonutils.nim b/lib/std/jsonutils.nim index 1e222e3a2..e33ae4401 100644 --- a/lib/std/jsonutils.nim +++ b/lib/std/jsonutils.nim @@ -48,13 +48,18 @@ type joptEnumOrd joptEnumSymbol joptEnumString + JsonNodeMode* = enum ## controls `toJson` for JsonNode types + joptJsonNodeAsRef ## returns the ref as is + joptJsonNodeAsCopy ## returns a deep copy of the JsonNode + joptJsonNodeAsObject ## treats JsonNode as a regular ref object ToJsonOptions* = object enumMode*: EnumMode - # xxx charMode + jsonNodeMode*: JsonNodeMode + # xxx charMode, etc proc initToJsonOptions*(): ToJsonOptions = ## initializes `ToJsonOptions` with sane options. - ToJsonOptions(enumMode: joptEnumOrd) + ToJsonOptions(enumMode: joptEnumOrd, jsonNodeMode: joptJsonNodeAsRef) proc isNamedTuple(T: typedesc): bool {.magic: "TypeTrait".} proc distinctBase(T: typedesc): typedesc {.magic: "TypeTrait".} @@ -286,8 +291,15 @@ proc toJson*[T](a: T, opt = initToJsonOptions()): JsonNode = result = newJArray() for v in a.fields: result.add toJson(v, opt) elif T is ref | ptr: - if system.`==`(a, nil): result = newJNull() - else: result = toJson(a[], opt) + template impl = + if system.`==`(a, nil): result = newJNull() + else: result = toJson(a[], opt) + when T is JsonNode: + case opt.jsonNodeMode + of joptJsonNodeAsRef: result = a + of joptJsonNodeAsCopy: result = copy(a) + of joptJsonNodeAsObject: impl() + else: impl() elif T is array | seq | set: result = newJArray() for ai in a: result.add toJson(ai, opt) diff --git a/tests/stdlib/tjsonutils.nim b/tests/stdlib/tjsonutils.nim index 62486b896..88c05facf 100644 --- a/tests/stdlib/tjsonutils.nim +++ b/tests/stdlib/tjsonutils.nim @@ -91,6 +91,28 @@ template fn() = doAssert b2.ord == 1 # explains the `1` testRoundtrip(a): """[1,2,3]""" + block: # JsonNode + let a = ((1, 2.5, "abc").toJson, (3, 4.5, "foo")) + testRoundtripVal(a): """[[1,2.5,"abc"],[3,4.5,"foo"]]""" + + block: + template toInt(a): untyped = cast[int](a) + + let a = 3.toJson + let b = (a, a) + + let c1 = b.toJson + doAssert c1[0].toInt == a.toInt + doAssert c1[1].toInt == a.toInt + + let c2 = b.toJson(ToJsonOptions(jsonNodeMode: joptJsonNodeAsCopy)) + doAssert c2[0].toInt != a.toInt + doAssert c2[1].toInt != c2[0].toInt + doAssert c2[1] == c2[0] + + let c3 = b.toJson(ToJsonOptions(jsonNodeMode: joptJsonNodeAsObject)) + doAssert $c3 == """[{"isUnquoted":false,"kind":2,"num":3},{"isUnquoted":false,"kind":2,"num":3}]""" + block: # ToJsonOptions let a = (me1, me2) doAssert $a.toJson() == "[1,2]"