mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-15 03:53:06 +00:00
json-rpc now requires nim-json-serialization to convert types to/from json. Use the nim-json-serialization signatures to call the json serialization lib from nim-codex (should be moved to its own lib)
404 lines
11 KiB
Nim
404 lines
11 KiB
Nim
|
|
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)
|