nim-serde/serde/json/serializer.nim

191 lines
4.5 KiB
Nim
Raw Normal View History

2024-02-07 09:40:48 +11:00
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
2024-02-07 14:39:52 +11:00
import ./stdjson
2024-02-07 09:40:48 +11:00
import ./pragmas
import ./types
export chronicles except toJson
2024-02-07 14:39:52 +11:00
export stdjson
2024-02-07 09:40:48 +11:00
export pragmas
export types
{.push raises: [].}
logScope:
topics = "nimserde json serializer"
proc `%`*(s: string): JsonNode =
2024-02-08 09:18:55 +11:00
newJString(s)
2024-02-07 09:40:48 +11:00
proc `%`*(n: uint): JsonNode =
2024-02-07 09:40:48 +11:00
if n > cast[uint](int.high):
newJString($n)
else:
newJInt(BiggestInt(n))
proc `%`*(n: int): JsonNode =
2024-02-08 09:18:55 +11:00
newJInt(n)
2024-02-07 09:40:48 +11:00
proc `%`*(n: BiggestUInt): JsonNode =
2024-02-07 09:40:48 +11:00
if n > cast[BiggestUInt](BiggestInt.high):
newJString($n)
else:
newJInt(BiggestInt(n))
proc `%`*(n: BiggestInt): JsonNode =
2024-02-08 09:18:55 +11:00
newJInt(n)
2024-02-07 09:40:48 +11:00
proc `%`*(n: float): JsonNode =
2024-02-08 09:18:55 +11:00
if n != n:
newJString("nan")
elif n == Inf:
newJString("inf")
elif n == -Inf:
newJString("-inf")
else:
newJFloat(n)
2024-02-07 09:40:48 +11:00
proc `%`*(b: bool): JsonNode =
2024-02-08 09:18:55 +11:00
newJBool(b)
2024-02-07 09:40:48 +11:00
proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
2024-02-08 09:18:55 +11:00
if keyVals.len == 0:
return newJArray()
2024-02-07 09:40:48 +11:00
let jObj = newJObject()
2024-02-08 09:18:55 +11:00
for key, val in items(keyVals):
jObj.fields[key] = val
2024-02-07 09:40:48 +11:00
jObj
2024-02-08 09:18:55 +11:00
template `%`*(j: JsonNode): JsonNode =
j
2024-02-07 09:40:48 +11:00
proc `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode =
2024-02-07 09:40:48 +11:00
let jObj = newJObject()
2024-02-08 09:18:55 +11:00
for k, v in table:
jObj[k] = ? %v
2024-02-07 09:40:48 +11:00
jObj
proc `%`*[T](opt: Option[T]): JsonNode =
2024-02-08 09:18:55 +11:00
if opt.isSome:
%(opt.get)
else:
newJNull()
2024-02-07 09:40:48 +11:00
proc `%`*[T: object or ref object](obj: T): JsonNode =
let jsonObj = newJObject()
2024-02-08 09:18:55 +11:00
let o =
when T is ref object:
obj[]
else:
obj
2024-02-07 09:40:48 +11:00
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"
2024-02-08 09:18:55 +11:00
case mode
2024-02-07 09:40:48 +11:00
of OptIn:
if not hasSerialize:
trace "object field not marked with serialize, skipping"
2024-02-07 09:40:48 +11:00
skip = true
elif opts.ignore:
skip = true
of OptOut:
if opts.ignore:
trace "object field opted out of serialization ('ignore' is set), skipping"
2024-02-07 09:40:48 +11:00
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
2024-02-08 09:18:55 +11:00
proc `%`*(o: enum): JsonNode =
% $o
2024-02-07 09:40:48 +11:00
proc `%`*(stint: StInt | StUint): JsonNode =
2024-02-08 09:18:55 +11:00
%stint.toString
2024-02-07 09:40:48 +11:00
proc `%`*(cstr: cstring): JsonNode =
2024-02-08 09:18:55 +11:00
% $cstr
2024-02-07 09:40:48 +11:00
proc `%`*(arr: openArray[byte]): JsonNode =
2024-02-08 09:18:55 +11:00
%arr.to0xHex
2024-02-07 09:40:48 +11:00
proc `%`*[T](elements: openArray[T]): JsonNode =
2024-02-07 09:40:48 +11:00
let jObj = newJArray()
2024-02-08 09:18:55 +11:00
for elem in elements:
jObj.add(%elem)
2024-02-07 09:40:48 +11:00
jObj
proc `%`*[T: distinct](id: T): JsonNode =
2024-02-07 09:40:48 +11:00
type baseType = T.distinctBase
2024-02-08 09:18:55 +11:00
%baseType(id)
2024-02-07 09:40:48 +11:00
2024-02-08 08:07:29 +11:00
proc toJson*[T](item: T, pretty = false): string =
if pretty:
(%item).pretty
else:
$(%item)
2024-02-07 09:40:48 +11:00
proc toJsnImpl(x: NimNode): NimNode =
case x.kind
of nnkBracket: # array
2024-02-08 09:18:55 +11:00
if x.len == 0:
return newCall(bindSym"newJArray")
2024-02-07 09:40:48 +11:00
result = newNimNode(nnkBracket)
for i in 0 ..< x.len:
result.add(toJsnImpl(x[i]))
result = newCall(bindSym("%", brOpen), result)
of nnkTableConstr: # object
2024-02-08 09:18:55 +11:00
if x.len == 0:
return newCall(bindSym"newJObject")
2024-02-07 09:40:48 +11:00
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:
2024-02-08 09:18:55 +11:00
if x.len == 1:
result = toJsnImpl(x[0])
else:
result = newCall(bindSym("%", brOpen), x)
2024-02-07 09:40:48 +11:00
else:
result = newCall(bindSym("%", brOpen), x)
macro `%*`*(x: untyped): JsonNode =
## Convert an expression to a JsonNode directly, without having to specify
## `%` for every element.
2024-02-08 09:18:55 +11:00
result = toJsnImpl(x)