jsonutils: add customization for toJson via ToJsonOptions; generalize symbolName; add symbolRank (#18029)

* jsonutils: add customization for toJson via `ToJsonOptions`

* add enumutils.symbolRank

* lookup table implementation for HoleyEnum

* cleanup

* changelog

* fixup

* Update lib/std/jsonutils.nim

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
This commit is contained in:
Timothee Cour 2021-05-18 06:10:19 -07:00 • committed by GitHub
commit 7f077a76fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 14 deletions

View file

@ -31,9 +31,11 @@ add a way to customize serialization, for e.g.:
]#
import macros
from enumutils import symbolName
from typetraits import OrdinalEnum
type
Joptions* = object
Joptions* = object # xxx rename FromJsonOptions
## Options controlling the behavior of `fromJson`.
allowExtraKeys*: bool
## If `true` Nim's object to which the JSON is parsed is not required to
@ -42,6 +44,17 @@ type
## If `true` Nim's object to which JSON is parsed is allowed to have
## fields without corresponding JSON keys.
# in future work: a key rename could be added
EnumMode* = enum
joptEnumOrd
joptEnumSymbol
joptEnumString
ToJsonOptions* = object
enumMode*: EnumMode
# xxx charMode
proc initToJsonOptions*(): ToJsonOptions =
## initializes `ToJsonOptions` with sane options.
ToJsonOptions(enumMode: joptEnumOrd)
proc isNamedTuple(T: typedesc): bool {.magic: "TypeTrait".}
proc distinctBase(T: typedesc): typedesc {.magic: "TypeTrait".}
@ -261,33 +274,41 @@ proc jsonTo*(b: JsonNode, T: typedesc, opt = Joptions()): T =
## reverse of `toJson`
fromJson(result, b, opt)
proc toJson*[T](a: T): JsonNode =
proc toJson*[T](a: T, opt = initToJsonOptions()): JsonNode =
## serializes `a` to json; uses `toJsonHook(a: T)` if it's in scope to
## customize serialization, see strtabs.toJsonHook for an example.
when compiles(toJsonHook(a)): result = toJsonHook(a)
elif T is object | tuple:
when T is object or isNamedTuple(T):
result = newJObject()
for k, v in a.fieldPairs: result[k] = toJson(v)
for k, v in a.fieldPairs: result[k] = toJson(v, opt)
else:
result = newJArray()
for v in a.fields: result.add toJson(v)
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[])
else: result = toJson(a[], opt)
elif T is array | seq | set:
result = newJArray()
for ai in a: result.add toJson(ai)
elif T is pointer: result = toJson(cast[int](a))
for ai in a: result.add toJson(ai, opt)
elif T is pointer: result = toJson(cast[int](a), opt)
# 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 distinct: result = toJson(a.distinctBase, opt)
elif T is bool: result = %(a)
elif T is SomeInteger: result = %a
elif T is Ordinal: result = %(a.ord)
elif T is enum:
when defined(nimLegacyJsonutilsHoleyEnum): result = %a
else: result = %(a.ord)
case opt.enumMode
of joptEnumOrd:
when T is Ordinal or not defined(nimLegacyJsonutilsHoleyEnum): %(a.ord)
else: toJson($a, opt)
of joptEnumSymbol:
when T is OrdinalEnum:
toJson(symbolName(a), opt)
else:
toJson($a, opt)
of joptEnumString: toJson($a, opt)
elif T is Ordinal: result = %(a.ord)
elif T is cstring: (if a == nil: result = newJNull() else: result = % $a)
else: result = %a