import std/json except `%`, `%*` import std/macros import std/options import std/strutils # import std/strformat import std/tables import std/typetraits import pkg/chronicles import pkg/contractabi import pkg/stew/byteutils import pkg/stint import pkg/questionable/results import ../../basics export json except `%`, `%*` {.push raises: [].} logScope: topics = "json serialization" type SerializationError = object of EthersError UnexpectedKindError = object of SerializationError template serialize* {.pragma.} proc mapErrTo[T, E1: CatchableError, E2: CatchableError](r: Result[T, E1], _: type E2): ?!T = r.mapErr(proc (e: E1): E2 = E2(msg: e.msg)) proc newUnexpectedKindError( expectedType: type, expectedKinds: string, json: JsonNode ): ref UnexpectedKindError = let kind = if json.isNil: "nil" else: $json.kind newException(UnexpectedKindError, "deserialization to " & $expectedType & " failed: expected " & expectedKinds & "but got " & $kind) proc newUnexpectedKindError( expectedType: type, expectedKinds: set[JsonNodeKind], json: JsonNode ): ref UnexpectedKindError = newUnexpectedKindError(expectedType, $expectedKinds, json) proc newUnexpectedKindError( expectedType: type, expectedKind: JsonNodeKind, json: JsonNode ): ref UnexpectedKindError = newUnexpectedKindError(expectedType, {expectedKind}, json) template expectJsonKind( expectedType: type, expectedKinds: set[JsonNodeKind], json: JsonNode ) = if json.isNil or json.kind notin expectedKinds: return failure(newUnexpectedKindError(expectedType, expectedKinds, json)) template expectJsonKind( expectedType: type, expectedKind: JsonNodeKind, json: JsonNode ) = expectJsonKind(expectedType, {expectedKind}, json) proc fromJson*( T: type enum, json: JsonNode ): ?!T = expectJsonKind(string, JString, json) catch parseEnum[T](json.str) proc fromJson*( _: type string, json: JsonNode ): ?!string = if json.isNil: let err = newException(ValueError, "'json' expected, but was nil") return failure(err) elif json.kind == JNull: return success("null") elif json.isNil or json.kind != JString: return failure(newUnexpectedKindError(string, JString, json)) catch json.getStr proc fromJson*( _: type bool, json: JsonNode ): ?!bool = expectJsonKind(bool, JBool, json) catch json.getBool proc fromJson*( _: type int, json: JsonNode ): ?!int = expectJsonKind(int, JInt, json) catch json.getInt proc fromJson*[T: SomeInteger]( _: type T, json: JsonNode ): ?!T = when T is uint|uint64 or (not defined(js) and int.sizeof == 4): expectJsonKind(T, {JInt, JString}, json) case json.kind of JString: let x = parseBiggestUInt(json.str) return success cast[T](x) else: return success T(json.num) else: expectJsonKind(T, {JInt}, json) return success cast[T](json.num) proc fromJson*[T: SomeFloat]( _: type T, json: JsonNode ): ?!T = expectJsonKind(T, {JInt, JFloat, JString}, json) if json.kind == JString: case json.str of "nan": let b = NaN return success T(b) # dst = NaN # would fail some tests because range conversions would cause CT error # in some cases; but this is not a hot-spot inside this branch and backend can optimize this. of "inf": let b = Inf return success T(b) of "-inf": let b = -Inf return success T(b) else: let err = newUnexpectedKindError(T, "'nan|inf|-inf'", json) return failure(err) else: if json.kind == JFloat: return success T(json.fnum) else: return success T(json.num) proc fromJson*( _: type seq[byte], json: JsonNode ): ?!seq[byte] = expectJsonKind(seq[byte], JString, json) hexToSeqByte(json.getStr).catch proc fromJson*[N: static[int], T: array[N, byte]]( _: type T, json: JsonNode ): ?!T = expectJsonKind(T, JString, json) T.fromHex(json.getStr).catch proc fromJson*[T: distinct]( _: type T, json: JsonNode ): ?!T = success T(? T.distinctBase.fromJson(json)) proc fromJson*[N: static[int], T: StUint[N]]( _: type T, json: JsonNode ): ?!T = expectJsonKind(T, JString, json) let jsonStr = json.getStr let prefix = jsonStr[0..1].toLowerAscii case prefix: of "0x": catch parse(jsonStr, T, 16) of "0o": catch parse(jsonStr, T, 8) of "0b": catch parse(jsonStr, T, 2) else: catch parse(jsonStr, T) proc fromJson*[T]( _: type Option[T], json: JsonNode ): ?! Option[T] = if json.isNil or json.kind == JNull: return success(none T) without val =? T.fromJson(json), error: return failure(error) success(val.some) proc fromJson*[T]( _: type seq[T], json: JsonNode ): ?! seq[T] = expectJsonKind(seq[T], JArray, json) var arr: seq[T] = @[] for elem in json.elems: arr.add(? T.fromJson(elem)) success arr proc fromJson*[T: ref object or object]( _: type T, json: JsonNode ): ?!T = when T is JsonNode: return success T(json) expectJsonKind(T, JObject, json) var res = when type(T) is ref: T.new() else: T.default # Leave this in, it's good for debugging: # trace "deserializing object", to = $T, json for name, value in fieldPairs(when type(T) is ref: res[] else: res): if jsonVal =? json{name}.catch and not jsonVal.isNil: without parsed =? type(value).fromJson(jsonVal), e: error "error deserializing field", field = $T & "." & name, json = jsonVal, error = e.msg return failure(e) value = parsed success(res) proc parse*(json: string): ?!JsonNode = try: echo "[json.parse] json: ", json return parseJson(json).catch except Exception as e: echo "[json.parse] exception: ", e.msg return err newException(CatchableError, e.msg) proc fromJson*[T: ref object or object]( _: type T, bytes: seq[byte] ): ?!T = let json = ? parse(string.fromBytes(bytes)) T.fromJson(json) # import std/streams # import std/parsejson # type StringStreamFixed = ref object of StringStream # closeImplFixed: proc (s: StringStreamFixed) # {.nimcall, raises: [IOError, OSError], tags: [WriteIOEffect], gcsafe.} # proc closeImpl*: proc (s: StringStreamFixed) # {.nimcall, raises: [IOError, OSError], tags: [WriteIOEffect], gcsafe.} = discard # proc ssCloseFixed(s: StringStreamFixed) = # # var s = StringStream(s) # s.data = "" # proc close*(s: StringStreamFixed) {.raises: [IOError, OSError].} = # ## Closes the stream `s`. # ## # ## See also: # ## * `flush proc <#flush,Stream>`_ # # runnableExamples: # # var strm = newStringStream("The first line\nthe second line\nthe third line") # # ## do something... # # strm.close() # if not isNil(s.closeImplFixed): s.closeImplFixed(s) # proc newStringStreamFixed*(s: sink string = ""): owned StringStreamFixed {.raises:[].} = # var ss = StringStreamFixed(newStringStream(s)) # ss.closeImplFixed = ssCloseFixed # ss # proc parseJson*(buffer: string; rawIntegers = false, rawFloats = false): JsonNode {.raises: [IOError, OSError, JsonParsingError, ValueError].} = # ## Parses JSON from `buffer`. # ## If `buffer` contains extra data, it will raise `JsonParsingError`. # ## If `rawIntegers` is true, integer literals will not be converted to a `JInt` # ## field but kept as raw numbers via `JString`. # ## If `rawFloats` is true, floating point literals will not be converted to a `JFloat` # ## field but kept as raw numbers via `JString`. # result = parseJson(newStringStreamFixed(buffer), "input", rawIntegers, rawFloats) proc fromJson*( _: type JsonNode, json: string ): ?!JsonNode = echo "[JsonNode.fromJson] json: ", json return parse(json) proc fromJson*[T: ref object or object]( _: type T, json: string ): ?!T = let json = ? parse(json) T.fromJson(json) func `%`*(s: string): JsonNode = newJString(s) func `%`*(n: uint): JsonNode = if n > cast[uint](int.high): newJString($n) else: newJInt(BiggestInt(n)) func `%`*(n: int): JsonNode = newJInt(n) func `%`*(n: BiggestUInt): JsonNode = if n > cast[BiggestUInt](BiggestInt.high): newJString($n) else: newJInt(BiggestInt(n)) func `%`*(n: BiggestInt): JsonNode = newJInt(n) func `%`*(n: float): JsonNode = if n != n: newJString("nan") elif n == Inf: newJString("inf") elif n == -Inf: newJString("-inf") else: newJFloat(n) func `%`*(b: bool): JsonNode = newJBool(b) func `%`*(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 func `%`*[T](table: Table[string, T]|OrderedTable[string, T]): JsonNode = let jObj = newJObject() for k, v in table: jObj[k] = ? %v jObj func `%`*[T](opt: Option[T]): JsonNode = if opt.isSome: %(opt.get) else: newJNull() func `%`*[T: object](obj: T): JsonNode = let jsonObj = newJObject() for name, value in obj.fieldPairs: when value.hasCustomPragma(serialize): jsonObj[name] = %value jsonObj func `%`*[T: ref object](obj: T): JsonNode = let jsonObj = newJObject() for name, value in obj[].fieldPairs: when value.hasCustomPragma(serialize): jsonObj[name] = %(value) jsonObj proc `%`*(o: enum): JsonNode = % $o func `%`*(stint: StInt|StUint): JsonNode = %stint.toString func `%`*(cstr: cstring): JsonNode = % $cstr func `%`*(arr: openArray[byte]): JsonNode = % arr.to0xHex func `%`*[T](elements: openArray[T]): JsonNode = let jObj = newJArray() for elem in elements: jObj.add(%elem) jObj func `%`*[T: distinct](id: T): JsonNode = type baseType = T.distinctBase % baseType(id) func toJson*[T](item: T): string = $(%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)