jsonutils: support cstring (including as Table key); improve docs (#16062)

* jsonutils: support cstring (including as Table key); improve docs
* changelog
* un-disable a test now that #16061 was fixed
This commit is contained in:
Timothee Cour 2021-03-31 09:24:39 -07:00 • committed by GitHub
commit 9b67e5c61b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 28 additions and 10 deletions

View file

@ -47,6 +47,8 @@
- Added an overload for the `collect` macro that inferes the container type based - Added an overload for the `collect` macro that inferes the container type based
on the syntax of the last expression. Works with std seqs, tables and sets. on the syntax of the last expression. Works with std seqs, tables and sets.
- `jsonutils` now handles `cstring` (including as Table key).
- Added `randState` template that exposes the default random number generator. - Added `randState` template that exposes the default random number generator.
Useful for library authors. Useful for library authors.

View file

@ -11,7 +11,7 @@ runnableExamples:
z1: int8 z1: int8
let a = (1.5'f32, (b: "b2", a: "a2"), 'x', @[Foo(t: true, z1: -3), nil], [{"name": "John"}.newStringTable]) let a = (1.5'f32, (b: "b2", a: "a2"), 'x', @[Foo(t: true, z1: -3), nil], [{"name": "John"}.newStringTable])
let j = a.toJson let j = a.toJson
doAssert j.jsonTo(typeof(a)).toJson == j assert j.jsonTo(typeof(a)).toJson == j
import std/[json,strutils,tables,sets,strtabs,options] import std/[json,strutils,tables,sets,strtabs,options]
@ -197,6 +197,11 @@ proc fromJson*[T](a: var T, b: JsonNode, opt = Joptions()) =
else: else:
a.distinctBase.fromJson(b) a.distinctBase.fromJson(b)
elif T is string|SomeNumber: a = to(b,T) elif T is string|SomeNumber: a = to(b,T)
elif T is cstring:
case b.kind
of JNull: a = nil
of JString: a = b.str
else: checkJson false, $($T, " ", b)
elif T is JsonNode: a = b elif T is JsonNode: a = b
elif T is ref | ptr: elif T is ref | ptr:
if b.kind == JNull: a = nil if b.kind == JNull: a = nil
@ -273,9 +278,10 @@ proc toJson*[T](a: T): JsonNode =
elif T is bool: result = %(a) elif T is bool: result = %(a)
elif T is SomeInteger: result = %a elif T is SomeInteger: result = %a
elif T is Ordinal: result = %(a.ord) elif T is Ordinal: result = %(a.ord)
elif T is cstring: (if a == nil: result = newJNull() else: result = % $a)
else: result = %a else: result = %a
proc fromJsonHook*[K, V](t: var (Table[K, V] | OrderedTable[K, V]), proc fromJsonHook*[K: string|cstring, V](t: var (Table[K, V] | OrderedTable[K, V]),
jsonNode: JsonNode) = jsonNode: JsonNode) =
## Enables `fromJson` for `Table` and `OrderedTable` types. ## Enables `fromJson` for `Table` and `OrderedTable` types.
## ##
@ -296,21 +302,27 @@ proc fromJsonHook*[K, V](t: var (Table[K, V] | OrderedTable[K, V]),
for k, v in jsonNode: for k, v in jsonNode:
t[k] = jsonTo(v, V) t[k] = jsonTo(v, V)
proc toJsonHook*[K, V](t: (Table[K, V] | OrderedTable[K, V])): JsonNode = proc toJsonHook*[K: string|cstring, V](t: (Table[K, V] | OrderedTable[K, V])): JsonNode =
## Enables `toJson` for `Table` and `OrderedTable` types. ## Enables `toJson` for `Table` and `OrderedTable` types.
## ##
## See also: ## See also:
## * `fromJsonHook proc<#fromJsonHook,,JsonNode>`_ ## * `fromJsonHook proc<#fromJsonHook,,JsonNode>`_
# pending PR #9217 use: toSeq(a) instead of `collect` in `runnableExamples`.
runnableExamples: runnableExamples:
import std/[tables, json] import std/[tables, json, sugar]
let foo = ( let foo = (
t: [("two", 2)].toTable, t: [("two", 2)].toTable,
ot: [("one", 1), ("three", 3)].toOrderedTable) ot: [("one", 1), ("three", 3)].toOrderedTable)
assert $toJson(foo) == """{"t":{"two":2},"ot":{"one":1,"three":3}}""" assert $toJson(foo) == """{"t":{"two":2},"ot":{"one":1,"three":3}}"""
# if keys are not string|cstring, you can use this:
let a = {10: "foo", 11: "bar"}.newOrderedTable
let a2 = collect: (for k,v in a: (k,v))
assert $toJson(a2) == """[[10,"foo"],[11,"bar"]]"""
result = newJObject() result = newJObject()
for k, v in pairs(t): for k, v in pairs(t):
result[k] = toJson(v) # not sure if $k has overhead for string
result[(when K is string: k else: $k)] = toJson(v)
proc fromJsonHook*[A](s: var SomeSet[A], jsonNode: JsonNode) = proc fromJsonHook*[A](s: var SomeSet[A], jsonNode: JsonNode) =
## Enables `fromJson` for `HashSet` and `OrderedSet` types. ## Enables `fromJson` for `HashSet` and `OrderedSet` types.

View file

@ -87,6 +87,7 @@ block largeSize: # longer than 4 characters
proc main() = proc main() =
doAssert hash(0.0) == hash(0) doAssert hash(0.0) == hash(0)
# bug #16061
doAssert hash(cstring"abracadabra") == 97309975 doAssert hash(cstring"abracadabra") == 97309975
doAssert hash(cstring"abracadabra") == hash("abracadabra") doAssert hash(cstring"abracadabra") == hash("abracadabra")

View file

@ -14,6 +14,7 @@ proc testRoundtrip[T](t: T, expected: string) =
doAssert t2.toJson == j doAssert t2.toJson == j
import tables, sets, algorithm, sequtils, options, strtabs import tables, sets, algorithm, sequtils, options, strtabs
from strutils import contains
type Foo = ref object type Foo = ref object
id: int id: int
@ -40,27 +41,29 @@ template fn() =
# https://github.com/nim-lang/Nim/issues/12282 # https://github.com/nim-lang/Nim/issues/12282
testRoundtrip(Foo(1.5)): """1.5""" testRoundtrip(Foo(1.5)): """1.5"""
block: block: # OrderedTable
testRoundtrip({"z": "Z", "y": "Y"}.toOrderedTable): """{"z":"Z","y":"Y"}""" testRoundtrip({"z": "Z", "y": "Y"}.toOrderedTable): """{"z":"Z","y":"Y"}"""
doAssert toJson({"z": 10, "": 11}.newTable).`$`.contains """"":11""" # allows hash to change
testRoundtrip({"z".cstring: 1, "".cstring: 2}.toOrderedTable): """{"z":1,"":2}"""
testRoundtrip({"z": (f1: 'f'), }.toTable): """{"z":{"f1":102}}""" testRoundtrip({"z": (f1: 'f'), }.toTable): """{"z":{"f1":102}}"""
block: block: # StringTable
testRoundtrip({"name": "John", "city": "Monaco"}.newStringTable): """{"mode":"modeCaseSensitive","table":{"city":"Monaco","name":"John"}}""" testRoundtrip({"name": "John", "city": "Monaco"}.newStringTable): """{"mode":"modeCaseSensitive","table":{"city":"Monaco","name":"John"}}"""
block: # complex example block: # complex example
let t = {"z": "Z", "y": "Y"}.newStringTable let t = {"z": "Z", "y": "Y"}.newStringTable
type A = ref object type A = ref object
a1: string a1: string
let a = (1.1, "fo", 'x', @[10,11], [true, false], [t,newStringTable()], [0'i8,3'i8], -4'i16, (foo: 0.5'f32, bar: A(a1: "abc"), bar2: A.default)) let a = (1.1, "fo", 'x', @[10,11], [true, false], [t,newStringTable()], [0'i8,3'i8], -4'i16, (foo: 0.5'f32, bar: A(a1: "abc"), bar2: A.default, cstring1: "foo", cstring2: "", cstring3: cstring(nil)))
testRoundtrip(a): 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}]""" """[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,"cstring1":"foo","cstring2":"","cstring3":null}]"""
block: block:
# edge case when user defined `==` doesn't handle `nil` well, e.g.: # edge case when user defined `==` doesn't handle `nil` well, e.g.:
# https://github.com/nim-lang/nimble/blob/63695f490728e3935692c29f3d71944d83bb1e83/src/nimblepkg/version.nim#L105 # https://github.com/nim-lang/nimble/blob/63695f490728e3935692c29f3d71944d83bb1e83/src/nimblepkg/version.nim#L105
testRoundtrip(@[Foo(id: 10), nil]): """[{"id":10},null]""" testRoundtrip(@[Foo(id: 10), nil]): """[{"id":10},null]"""
block: block: # enum
type Foo = enum f1, f2, f3, f4, f5 type Foo = enum f1, f2, f3, f4, f5
type Bar = enum b1, b2, b3, b4 type Bar = enum b1, b2, b3, b4
let a = [f2: b2, f3: b3, f4: b4] let a = [f2: b2, f3: b3, f4: b4]