summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2020-06-15 04:22:43 -0700
committerGitHub <noreply@github.com>2020-06-15 13:22:43 +0200
commitd51beb7b20c7670a17769b30e721fe67761f98e6 (patch)
tree8cf5004199932ce1952515365928d07004378a2d
parentbf604c6829f87a551dc3b5ad836679be4ab53789 (diff)
downloadNim-d51beb7b20c7670a17769b30e721fe67761f98e6.tar.gz
make `fromJson/toJson` work with `array[range, typ]`, + 1 bugfix (#14669)
* make toJson more robust

* properly handle array
-rw-r--r--lib/std/jsonutils.nim32
-rw-r--r--tests/stdlib/tjsonutils.nim26
2 files changed, 50 insertions, 8 deletions
diff --git a/lib/std/jsonutils.nim b/lib/std/jsonutils.nim
index bfa600fa9..be3d7e7c8 100644
--- a/lib/std/jsonutils.nim
+++ b/lib/std/jsonutils.nim
@@ -2,9 +2,17 @@
 This module implements a hookable (de)serialization for arbitrary types.
 Design goal: avoid importing modules where a custom serialization is needed;
 see strtabs.fromJsonHook,toJsonHook for an example.
-
 ]##
 
+runnableExamples:
+  import std/[strtabs,json]
+  type Foo = ref object
+    t: bool
+    z1: int8
+  let a = (1.5'f32, (b: "b2", a: "a2"), 'x', @[Foo(t: true, z1: -3), nil], [{"name": "John"}.newStringTable])
+  let j = a.toJson
+  doAssert j.jsonTo(type(a)).toJson == j
+
 import std/[json,tables,strutils]
 
 #[
@@ -12,10 +20,14 @@ xxx
 use toJsonHook,fromJsonHook for Table|OrderedTable
 add Options support also using toJsonHook,fromJsonHook and remove `json=>options` dependency
 
-future direction:
-add a way to customize serialization, for eg allowing missing
-or extra fields in JsonNode, field renaming, and a way to handle cyclic references
-using a cache of already visited addresses.
+Future directions:
+add a way to customize serialization, for eg:
+* allowing missing or extra fields in JsonNode
+* field renaming
+* allow serializing `enum` and `char` as `string` instead of `int`
+  (enum is more compact/efficient, and robust to enum renamings, but string
+  is more human readable)
+* handle cyclic references, using a cache of already visited addresses
 ]#
 
 proc isNamedTuple(T: typedesc): bool {.magic: "TypeTrait".}
@@ -65,8 +77,10 @@ proc fromJson*[T](a: var T, b: JsonNode) =
       fromJson(a[], b)
   elif T is array:
     checkJson a.len == b.len, $(a.len, b.len, $T)
-    for i, val in b.getElems:
-      fromJson(a[i], val)
+    var i = 0
+    for ai in mitems(a):
+      fromJson(ai, b[i])
+      i.inc
   elif T is seq:
     a.setLen b.len
     for i, val in b.getElems:
@@ -114,12 +128,14 @@ proc toJson*[T](a: T): JsonNode =
       result = newJArray()
       for v in a.fields: result.add toJson(v)
   elif T is ref | ptr:
-    if a == nil: result = newJNull()
+    if system.`==`(a, nil): result = newJNull()
     else: result = toJson(a[])
   elif T is array | seq:
     result = newJArray()
     for ai in a: result.add toJson(ai)
   elif T is pointer: result = toJson(cast[int](a))
+    # edge case: `a == nil` could've also led to `newJNull()`, but this results
+    # in simpler code for `toJson` and `fromJson`.
   elif T is distinct: result = toJson(a.distinctBase)
   elif T is bool: result = %(a)
   elif T is Ordinal: result = %(a.ord)
diff --git a/tests/stdlib/tjsonutils.nim b/tests/stdlib/tjsonutils.nim
index 01c6aa05a..fca980dc9 100644
--- a/tests/stdlib/tjsonutils.nim
+++ b/tests/stdlib/tjsonutils.nim
@@ -13,12 +13,26 @@ proc testRoundtrip[T](t: T, expected: string) =
 import tables
 import strtabs
 
+type Foo = ref object
+  id: int
+
+proc `==`(a, b: Foo): bool =
+  a.id == b.id
+
 template fn() = 
   block: # toJson, jsonTo
     type Foo = distinct float
     testRoundtrip('x', """120""")
     when not defined(js):
       testRoundtrip(cast[pointer](12345)): """12345"""
+      when nimvm:
+        discard
+        # bugs:
+        # Error: unhandled exception: 'intVal' is not accessible using discriminant 'kind' of type 'TNode' [
+        # Error: VM does not support 'cast' from tyNil to tyPointer
+      else:
+        testRoundtrip(pointer(nil)): """0"""
+        testRoundtrip(cast[pointer](nil)): """0"""
 
     # causes workaround in `fromJson` potentially related to
     # https://github.com/nim-lang/Nim/issues/12282
@@ -40,5 +54,17 @@ template fn() =
     testRoundtrip(a):
       """[1.1,"fo",120,[10,11],[true,false],[{"mode":"modeCaseSensitive","table":{"y":"Y","z":"Z"}},{"mode":"modeCaseSensitive","table":{}}],[0,3],-4,{"foo":0.5,"bar":{"a1":"abc"},"bar2":null}]"""
 
+  block:
+    # edge case when user defined `==` doesn't handle `nil` well, eg:
+    # https://github.com/nim-lang/nimble/blob/63695f490728e3935692c29f3d71944d83bb1e83/src/nimblepkg/version.nim#L105
+    testRoundtrip(@[Foo(id: 10), nil]): """[{"id":10},null]"""
+
+  block:
+    type Foo = enum f1, f2, f3, f4, f5
+    type Bar = enum b1, b2, b3, b4
+    let a = [f2: b2, f3: b3, f4: b4]
+    doAssert b2.ord == 1 # explains the `1`
+    testRoundtrip(a): """[1,2,3]"""
+
 static: fn()
 fn()