mirror of
https://github.com/logos-storage/nim-serde.git
synced 2026-01-02 13:43:06 +00:00
191 lines
4.5 KiB
Nim
191 lines
4.5 KiB
Nim
import std/macros
|
|
import std/options
|
|
import std/strutils
|
|
import std/tables
|
|
import std/typetraits
|
|
|
|
import pkg/chronicles except toJson
|
|
import pkg/questionable
|
|
import pkg/stew/byteutils
|
|
import pkg/stint
|
|
|
|
import ../utils/stdjson
|
|
import ../utils/pragmas
|
|
import ../utils/types
|
|
|
|
export chronicles except toJson
|
|
export stdjson
|
|
export pragmas
|
|
export types
|
|
|
|
{.push raises: [].}
|
|
|
|
logScope:
|
|
topics = "nimserde json serializer"
|
|
|
|
proc `%`*(s: string): JsonNode =
|
|
newJString(s)
|
|
|
|
proc `%`*(n: uint): JsonNode =
|
|
if n > cast[uint](int.high):
|
|
newJString($n)
|
|
else:
|
|
newJInt(BiggestInt(n))
|
|
|
|
proc `%`*(n: int): JsonNode =
|
|
newJInt(n)
|
|
|
|
proc `%`*(n: BiggestUInt): JsonNode =
|
|
if n > cast[BiggestUInt](BiggestInt.high):
|
|
newJString($n)
|
|
else:
|
|
newJInt(BiggestInt(n))
|
|
|
|
proc `%`*(n: BiggestInt): JsonNode =
|
|
newJInt(n)
|
|
|
|
proc `%`*(n: float): JsonNode =
|
|
if n != n:
|
|
newJString("nan")
|
|
elif n == Inf:
|
|
newJString("inf")
|
|
elif n == -Inf:
|
|
newJString("-inf")
|
|
else:
|
|
newJFloat(n)
|
|
|
|
proc `%`*(b: bool): JsonNode =
|
|
newJBool(b)
|
|
|
|
proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
|
|
if keyVals.len == 0:
|
|
return newJArray()
|
|
let jObj = newJObject()
|
|
for key, val in items(keyVals):
|
|
jObj.fields[key] = val
|
|
jObj
|
|
|
|
template `%`*(j: JsonNode): JsonNode =
|
|
j
|
|
|
|
proc `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode =
|
|
let jObj = newJObject()
|
|
for k, v in table:
|
|
jObj[k] = ? %v
|
|
jObj
|
|
|
|
proc `%`*[T](opt: Option[T]): JsonNode =
|
|
if opt.isSome:
|
|
%(opt.get)
|
|
else:
|
|
newJNull()
|
|
|
|
proc `%`*[T: object or ref object](obj: T): JsonNode =
|
|
let jsonObj = newJObject()
|
|
let o =
|
|
when T is ref object:
|
|
obj[]
|
|
else:
|
|
obj
|
|
|
|
let mode = T.getSerdeMode(serialize)
|
|
|
|
for name, value in o.fieldPairs:
|
|
logScope:
|
|
field = $T & "." & name
|
|
mode
|
|
|
|
let opts = getSerdeFieldOptions(serialize, name, value)
|
|
let hasSerialize = value.hasCustomPragma(serialize)
|
|
var skip = false # workaround for 'continue' not supported in a 'fields' loop
|
|
|
|
# logScope moved into proc due to chronicles issue: https://github.com/status-im/nim-chronicles/issues/148
|
|
logScope:
|
|
topics = "serde json serialization"
|
|
|
|
case mode
|
|
of OptIn:
|
|
if not hasSerialize:
|
|
trace "object field not marked with serialize, skipping"
|
|
skip = true
|
|
elif opts.ignore:
|
|
skip = true
|
|
of OptOut:
|
|
if opts.ignore:
|
|
trace "object field opted out of serialization ('ignore' is set), skipping"
|
|
skip = true
|
|
elif hasSerialize and opts.key == name: # all serialize params are default
|
|
warn "object field marked as serialize in OptOut mode, but 'ignore' not set, field will be serialized"
|
|
of Strict:
|
|
if opts.ignore:
|
|
# unable to figure out a way to make this a compile time check
|
|
warn "object field marked as 'ignore' while in Strict mode, field will be serialized anyway"
|
|
|
|
if not skip:
|
|
jsonObj[opts.key] = %value
|
|
|
|
jsonObj
|
|
|
|
proc `%`*(o: enum): JsonNode =
|
|
% $o
|
|
|
|
proc `%`*(stint: StInt | StUint): JsonNode =
|
|
%stint.toString
|
|
|
|
proc `%`*(cstr: cstring): JsonNode =
|
|
% $cstr
|
|
|
|
proc `%`*(arr: openArray[byte]): JsonNode =
|
|
%arr.to0xHex
|
|
|
|
proc `%`*[T](elements: openArray[T]): JsonNode =
|
|
let jObj = newJArray()
|
|
for elem in elements:
|
|
jObj.add(%elem)
|
|
jObj
|
|
|
|
proc `%`*[T: distinct](id: T): JsonNode =
|
|
type baseType = T.distinctBase
|
|
%baseType(id)
|
|
|
|
proc toJson*[T](item: T, pretty = false): string =
|
|
if pretty:
|
|
(%item).pretty
|
|
else:
|
|
$(%item)
|
|
|
|
proc toJsnImpl(x: NimNode): NimNode =
|
|
case x.kind
|
|
of nnkBracket: # array
|
|
if x.len == 0:
|
|
return newCall(bindSym"newJArray")
|
|
result = newNimNode(nnkBracket)
|
|
for i in 0 ..< x.len:
|
|
result.add(toJsnImpl(x[i]))
|
|
result = newCall(bindSym("%", brOpen), result)
|
|
of nnkTableConstr: # object
|
|
if x.len == 0:
|
|
return newCall(bindSym"newJObject")
|
|
result = newNimNode(nnkTableConstr)
|
|
for i in 0 ..< x.len:
|
|
x[i].expectKind nnkExprColonExpr
|
|
result.add newTree(nnkExprColonExpr, x[i][0], toJsnImpl(x[i][1]))
|
|
result = newCall(bindSym("%", brOpen), result)
|
|
of nnkCurly: # empty object
|
|
x.expectLen(0)
|
|
result = newCall(bindSym"newJObject")
|
|
of nnkNilLit:
|
|
result = newCall(bindSym"newJNull")
|
|
of nnkPar:
|
|
if x.len == 1:
|
|
result = toJsnImpl(x[0])
|
|
else:
|
|
result = newCall(bindSym("%", brOpen), x)
|
|
else:
|
|
result = newCall(bindSym("%", brOpen), x)
|
|
|
|
macro `%*`*(x: untyped): JsonNode =
|
|
## Convert an expression to a JsonNode directly, without having to specify
|
|
## `%` for every element.
|
|
result = toJsnImpl(x)
|