Add json de/serialization lib from codex to handle conversions

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)
This commit is contained in:
Eric 2024-01-24 11:53:53 +11:00
parent fd16d71ea5
commit daa88cc8bf
No known key found for this signature in database
3 changed files with 722 additions and 155 deletions

View File

@ -3,6 +3,7 @@ import pkg/stew/byteutils
import ./basics
import ./transaction
import ./blocktag
import ./providers/jsonrpc/json
export basics
export transaction
@ -15,19 +16,19 @@ type
ProviderError* = object of EthersError
Subscription* = ref object of RootObj
EventFilter* = ref object of RootObj
address*: Address
topics*: seq[Topic]
address* {.serialize.}: Address
topics* {.serialize.}: seq[Topic]
Filter* = ref object of EventFilter
fromBlock*: BlockTag
toBlock*: BlockTag
fromBlock* {.serialize.}: BlockTag
toBlock* {.serialize.}: BlockTag
FilterByBlockHash* = ref object of EventFilter
blockHash*: BlockHash
blockHash* {.serialize.}: BlockHash
Log* = object
blockNumber*: UInt256
data*: seq[byte]
logIndex*: UInt256
removed*: bool
topics*: seq[Topic]
blockNumber* {.serialize.}: UInt256
data* {.serialize.}: seq[byte]
logIndex* {.serialize.}: UInt256
removed* {.serialize.}: bool
topics* {.serialize.}: seq[Topic]
TransactionHash* = array[32, byte]
BlockHash* = array[32, byte]
TransactionStatus* = enum
@ -35,45 +36,45 @@ type
Success = 1,
Invalid = 2
TransactionResponse* = object
provider*: Provider
hash*: TransactionHash
provider* {.serialize.}: Provider
hash* {.serialize.}: TransactionHash
TransactionReceipt* = object
sender*: ?Address
to*: ?Address
contractAddress*: ?Address
transactionIndex*: UInt256
gasUsed*: UInt256
logsBloom*: seq[byte]
blockHash*: ?BlockHash
transactionHash*: TransactionHash
logs*: seq[Log]
blockNumber*: ?UInt256
cumulativeGasUsed*: UInt256
effectiveGasPrice*: ?UInt256
sender* {.serialize.}: ?Address
to* {.serialize.}: ?Address
contractAddress* {.serialize.}: ?Address
transactionIndex* {.serialize.}: UInt256
gasUsed* {.serialize.}: UInt256
logsBloom* {.serialize.}: seq[byte]
blockHash* {.serialize.}: ?BlockHash
transactionHash* {.serialize.}: TransactionHash
logs* {.serialize.}: seq[Log]
blockNumber* {.serialize.}: ?UInt256
cumulativeGasUsed* {.serialize.}: UInt256
effectiveGasPrice* {.serialize.}: ?UInt256
status*: TransactionStatus
transactionType*: TransactionType
`type`* {.serialize.}: TransactionType
LogHandler* = proc(log: Log) {.gcsafe, raises:[].}
BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].}
Topic* = array[32, byte]
Block* = object
number*: ?UInt256
timestamp*: UInt256
hash*: ?BlockHash
number* {.serialize.}: ?UInt256
timestamp* {.serialize.}: UInt256
hash* {.serialize.}: ?BlockHash
PastTransaction* = object
blockHash*: BlockHash
blockNumber*: UInt256
sender*: Address
gas*: UInt256
gasPrice*: UInt256
hash*: TransactionHash
input*: seq[byte]
nonce*: UInt256
to*: Address
transactionIndex*: UInt256
transactionType*: ?TransactionType
chainId*: ?UInt256
value*: UInt256
v*, r*, s*: UInt256
blockHash* {.serialize.}: BlockHash
blockNumber* {.serialize.}: UInt256
sender* {.serialize.}: Address
gas* {.serialize.}: UInt256
gasPrice* {.serialize.}: UInt256
hash* {.serialize.}: TransactionHash
input* {.serialize.}: seq[byte]
nonce* {.serialize.}: UInt256
to* {.serialize.}: Address
transactionIndex* {.serialize.}: UInt256
`type`* {.serialize.}: ?TransactionType
chainId* {.serialize.}: ?UInt256
value* {.serialize.}: UInt256
v* {.serialize.}, r* {.serialize.}, s* {.serialize.}: UInt256
const EthersDefaultConfirmations* {.intdefine.} = 12
const EthersReceiptTimeoutBlks* {.intdefine.} = 50 # in blocks
@ -91,7 +92,7 @@ func toTransaction*(past: PastTransaction): Transaction =
data: past.input,
nonce: some past.nonce,
to: past.to,
transactionType: past.transactionType,
`type`: past.`type`,
gasLimit: some past.gas,
chainId: past.chainId
)

View File

@ -1,71 +1,148 @@
import std/json
# import std/json
import std/strformat
import std/strutils
import std/sugar
# import pkg/eth/common/eth_types_json_serialization
import pkg/chronicles except fromJson, `%`, `%*`, toJson
import pkg/json_rpc/jsonmarshal
import pkg/questionable/results
import pkg/stew/byteutils
import ../../basics
import ../../transaction
import ../../blocktag
import ../../provider
import ./json
export jsonmarshal
export json
export chronicles except fromJson, `%`, `%*`, toJson
# {.push raises: [].}
type JsonSerializationError = object of EthersError
func toException*(v: ref CatchableError): ref SerializationError = (ref SerializationError)(msg: v.msg)
template raiseSerializationError(message: string) =
raise newException(JsonSerializationError, message)
proc getOrRaise*[T, E](self: ?!T, exc: typedesc[E]): T {.raises: [E].} =
let val = self.valueOr:
raise newException(E, self.error.msg)
val
proc expectFields(json: JsonNode, expectedFields: varargs[string]) =
for fieldName in expectedFields:
if not json.hasKey(fieldName):
raiseSerializationError(fmt"'{fieldName}' field not found in ${json}")
raiseSerializationError("'" & fieldName & "' field not found in " & $json)
# func fromJson*(T: type, json: JsonNode): T
# func fromJson*[T](json: JsonNode, result: var Option[T])
# func fromJson*[T](json: JsonNode, result: var seq[T])
# func fromJson*(json: JsonNode, result var T): T
func fromJson*(T: type, json: JsonNode, name = ""): T =
fromJson(json, name, result)
# byte sequence
func `%`*(bytes: seq[byte]): JsonNode =
%("0x" & bytes.toHex)
# func `%`*(bytes: seq[byte]): JsonNode =
# %("0x" & bytes.toHex)
func fromJson*(json: JsonNode, name: string, result: var seq[byte]) =
result = hexToSeqByte(json.getStr())
# func fromJson*(json: JsonNode, result: var seq[byte]) =
# result = hexToSeqByte(json.getStr())
# byte arrays
func `%`*[N](bytes: array[N, byte]): JsonNode =
%("0x" & bytes.toHex)
# func `%`*[N](bytes: array[N, byte]): JsonNode =
# %("0x" & bytes.toHex)
func fromJson*[N](json: JsonNode, name: string, result: var array[N, byte]) =
hexToByteArray(json.getStr(), result)
# func fromJson*[N](json: JsonNode, result: var array[N, byte]) =
# hexToByteArray(json.getStr(), result)
# func fromJson*[N](json: JsonNode, result: var seq[array[N, byte]]) =
# for elem in json.getElems:
# var byteArr: array[N, byte]
# fromJson(elem, byteArr)
# result.add byteArr
# Address
func `%`*(address: Address): JsonNode =
%($address)
func fromJson*(json: JsonNode, name: string, result: var Address) =
if address =? Address.init(json.getStr()):
result = address
else:
raise newException(ValueError, "\"" & name & "\"is not an Address")
# func fromJson(jsonVal: string, result: var Address) =
# without address =? Address.init(jsonVal):
# raiseSerializationError "Failed to convert '" & jsonVal & "' to Address"
# result = address
# func fromJson*(json: JsonNode, result: var Address) =
# let val = json.getStr
# fromJson(val, result)
proc readValue*(r: var JsonReader[JrpcConv], result: var Address)
{.raises: [SerializationError, IOError].} =
var val = r.readValue(string)
without address =? Address.init(val):
raiseSerializationError "Failed to convert '" & val & "' to Address"
result = address
proc writeValue*(
writer: var JsonWriter[JrpcConv],
value: Address
) {.raises:[IOError].} =
writer.writeValue(%value)
# Filter
func `%`*(filter: Filter): JsonNode =
%*{
"fromBlock": filter.fromBlock,
"toBlock": filter.toBlock
}
proc writeValue*(
writer: var JsonWriter[JrpcConv],
value: Filter
) {.raises:[IOError].} =
writer.writeValue(%value)
# EventFilter
func `%`*(filter: EventFilter): JsonNode =
%*{
"address": filter.address,
"topics": filter.topics
}
proc writeValue*(
writer: var JsonWriter[JrpcConv],
value: EventFilter
) {.raises:[IOError].} =
writer.writeValue(%value)
# UInt256
func `%`*(integer: UInt256): JsonNode =
%("0x" & toHex(integer))
# func `%`*(integer: UInt256): JsonNode =
# %("0x" & toHex(integer))
func fromJson*(json: JsonNode, name: string, result: var UInt256) =
result = UInt256.fromHex(json.getStr())
# func fromJson*(json: JsonNode, result: var UInt256) =
# result = UInt256.fromHex(json.getStr())
# TransactionType
proc writeValue*(
w: var JsonWriter, value: StUint) {.inline, raises: [IOError].} =
w.writeValue $value
func fromJson*(json: JsonNode, name: string, result: var TransactionType) =
let val = fromHex[int](json.getStr)
result = TransactionType(val)
proc readValue*(
r: var JsonReader, value: var StUint
) {.inline, raises: [IOError, SerializationError].} =
let json = r.readValue(JsonNode)
value = typeof(value).fromJson(json).getOrRaise(SerializationError)
func `%`*(txType: TransactionType): JsonNode =
%("0x" & txType.int.toHex(1))
# # TransactionType
# func fromJson*(json: JsonNode, result: var TransactionType) =
# let val = fromHex[int](json.getStr)
# result = TransactionType(val)
# func `%`*(txType: TransactionType): JsonNode =
# %("0x" & txType.int.toHex(1))
# Transaction
@ -82,107 +159,193 @@ func `%`*(transaction: Transaction): JsonNode =
if gasLimit =? transaction.gasLimit:
result["gas"] = %gasLimit
proc writeValue*(
writer: var JsonWriter[JrpcConv],
value: Transaction
) {.raises:[IOError].} =
writer.writeValue(%value)
# Block
# func fromJson*(json: JsonNode, result: var Block) {.raises: [JsonSerializationError].} =
# var number: ?UInt256
# var timestamp: UInt256
# var hash: ?BlockHash
# expectFields json, "number", "timestamp", "hash"
# fromJson(json{"number"}, number)
# fromJson(json{"timestamp"}, timestamp)
# fromJson(json{"hash"}, hash)
# result = Block(number: number, timestamp: timestamp, hash: hash)
proc readValue*(r: var JsonReader[JrpcConv], result: var Option[Block])
{.raises: [SerializationError, IOError].} =
var json = r.readValue(JsonNode)
if json.isNil or json.kind == JNull:
result = none Block
# result = some Block()
# result.number = Json.decode(result{"number"}, Option[UInt256])
result = Option[Block].fromJson(json).getOrRaise(SerializationError)
# without val =? Option[Block].fromJson(json) #.mapErr(e => newException(SerializationError, e.msg))
# without blk =? Option[Block].fromJson(json), error:
# warn "failed to deserialize into Option[Block]", json
# result = none Block
# result = blk
# if json.isNil or json.kind == JNull:
# result = none Block
# var res: Block
# fromJson(Block, json, res)
# result = some res
# BlockTag
func `%`*(blockTag: BlockTag): JsonNode =
%($blockTag)
# func `%`*(blockTag: BlockTag): JsonNode =
# %($blockTag)
proc writeValue*(
writer: var JsonWriter[JrpcConv],
value: BlockTag
) {.raises:[IOError].} =
writer.writeValue($value)
proc readValue*(r: var JsonReader[JrpcConv],
result: var BlockTag) {.raises:[SerializationError, IOError].} =
var json = r.readValue(JsonNode)
result = BlockTag.fromJson(json).getOrRaise(SerializationError)
# Log
func fromJson*(json: JsonNode, name: string, result: var Log) =
if not (json.hasKey("data") and json.hasKey("topics")):
raise newException(ValueError, "'data' and/or 'topics' fields not found")
# func fromJson*(json: JsonNode, result: var Log) =
# expectFields json, "data", "topics"
var data: seq[byte]
var topics: seq[Topic]
fromJson(json["data"], "data", data)
fromJson(json["topics"], "topics", topics)
result = Log(data: data, topics: topics)
# var data: seq[byte]
# var topics: seq[Topic]
# fromJson(json["data"], data)
# fromJson(json["topics"], topics)
# result = Log(data: data, topics: topics)
# TransactionStatus
func fromJson*(json: JsonNode, name: string, result: var TransactionStatus) =
let val = fromHex[int](json.getStr)
result = TransactionStatus(val)
# func fromJson*(json: JsonNode, result: var TransactionStatus) =
# let val = fromHex[int](json.getStr)
# result = TransactionStatus(val)
func `%`*(status: TransactionStatus): JsonNode =
%("0x" & status.int.toHex(1))
# func `%`*(status: TransactionStatus): JsonNode =
# %("0x" & status.int.toHex(1))
# PastTransaction
func fromJson*(json: JsonNode, name: string, result: var PastTransaction) =
# Deserializes a past transaction, eg eth_getTransactionByHash.
# Spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash
json.expectFields "blockHash", "blockNumber", "from", "gas", "gasPrice",
"hash", "input", "nonce", "to", "transactionIndex", "value",
"v", "r", "s"
proc readValue*(r: var JsonReader[JrpcConv], result: var Option[PastTransaction])
{.raises: [SerializationError, IOError].} =
var json = r.readValue(JsonNode)
result = Option[PastTransaction].fromJson(json).getOrRaise(SerializationError)
result = PastTransaction(
blockHash: BlockHash.fromJson(json["blockHash"], "blockHash"),
blockNumber: UInt256.fromJson(json["blockNumber"], "blockNumber"),
sender: Address.fromJson(json["from"], "from"),
gas: UInt256.fromJson(json["gas"], "gas"),
gasPrice: UInt256.fromJson(json["gasPrice"], "gasPrice"),
hash: TransactionHash.fromJson(json["hash"], "hash"),
input: seq[byte].fromJson(json["input"], "input"),
nonce: UInt256.fromJson(json["nonce"], "nonce"),
to: Address.fromJson(json["to"], "to"),
transactionIndex: UInt256.fromJson(json["transactionIndex"], "transactionIndex"),
value: UInt256.fromJson(json["value"], "value"),
v: UInt256.fromJson(json["v"], "v"),
r: UInt256.fromJson(json["r"], "r"),
s: UInt256.fromJson(json["s"], "s"),
)
if json.hasKey("type"):
result.transactionType = fromJson(?TransactionType, json["type"], "type")
if json.hasKey("chainId"):
result.chainId = fromJson(?UInt256, json["chainId"], "chainId")
# func fromJson*(json: JsonNode, result: var PastTransaction) =
# # Deserializes a past transaction, eg eth_getTransactionByHash.
# # Spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash
# json.expectFields "blockHash", "blockNumber", "from", "gas", "gasPrice",
# "hash", "input", "nonce", "to", "transactionIndex", "value",
# "v", "r", "s"
func `%`*(tx: PastTransaction): JsonNode =
let json = %*{
"blockHash": tx.blockHash,
"blockNumber": tx.blockNumber,
"from": tx.sender,
"gas": tx.gas,
"gasPrice": tx.gasPrice,
"hash": tx.hash,
"input": tx.input,
"nonce": tx.nonce,
"to": tx.to,
"transactionIndex": tx.transactionIndex,
"value": tx.value,
"v": tx.v,
"r": tx.r,
"s": tx.s
}
if txType =? tx.transactionType:
json["type"] = %txType
if chainId =? tx.chainId:
json["chainId"] = %chainId
return json
# result = PastTransaction(
# blockHash: BlockHash.fromJson(json["blockHash"]), #, "blockHash"),
# blockNumber: UInt256.fromJson(json["blockNumber"]), #, "blockNumber"),
# sender: Address.fromJson(json["from"]), #, "from"),
# gas: UInt256.fromJson(json["gas"]), #, "gas"),
# gasPrice: UInt256.fromJson(json["gasPrice"]), #, "gasPrice"),
# hash: TransactionHash.fromJson(json["hash"]), #, "hash"),
# input: seq[byte].fromJson(json["input"]), #, "input"),
# nonce: UInt256.fromJson(json["nonce"]), #, "nonce"),
# to: Address.fromJson(json["to"]), #, "to"),
# transactionIndex: UInt256.fromJson(json["transactionIndex"]), #, "transactionIndex"),
# value: UInt256.fromJson(json["value"]), #, "value"),
# v: UInt256.fromJson(json["v"]), #, "v"),
# r: UInt256.fromJson(json["r"]), #, "r"),
# s: UInt256.fromJson(json["s"]) #, "s"),
# )
# if json.hasKey("type"):
# result.transactionType = fromJson(?TransactionType, json["type"], "type")
# if json.hasKey("chainId"):
# result.chainId = fromJson(?UInt256, json["chainId"], "chainId")
# func `%`*(tx: PastTransaction): JsonNode =
# let json = %*{
# "blockHash": tx.blockHash,
# "blockNumber": tx.blockNumber,
# "from": tx.sender,
# "gas": tx.gas,
# "gasPrice": tx.gasPrice,
# "hash": tx.hash,
# "input": tx.input,
# "nonce": tx.nonce,
# "to": tx.to,
# "transactionIndex": tx.transactionIndex,
# "value": tx.value,
# "v": tx.v,
# "r": tx.r,
# "s": tx.s
# }
# if txType =? tx.transactionType:
# json["type"] = %txType
# if chainId =? tx.chainId:
# json["chainId"] = %chainId
# return json
# TransactionReceipt
func fromJson*(json: JsonNode, name: string, result: var TransactionReceipt) =
# Deserializes a transaction receipt, eg eth_getTransactionReceipt.
# Spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
json.expectFields "transactionHash", "transactionIndex", "cumulativeGasUsed",
"effectiveGasPrice", "gasUsed", "logs", "logsBloom", "type",
"status"
proc readValue*(r: var JsonReader[JrpcConv], result: var Option[TransactionReceipt])
{.raises: [SerializationError, IOError].} =
var json = r.readValue(JsonNode)
result = Option[TransactionReceipt].fromJson(json).getOrRaise(SerializationError)
result = TransactionReceipt(
transactionHash: fromJson(TransactionHash, json["transactionHash"], "transactionHash"),
transactionIndex: UInt256.fromJson(json["transactionIndex"], "transactionIndex"),
blockHash: fromJson(?BlockHash, json["blockHash"], "blockHash"),
blockNumber: fromJson(?UInt256, json["blockNumber"], "blockNumber"),
sender: fromJson(?Address, json["from"], "from"),
to: fromJson(?Address, json["to"], "to"),
cumulativeGasUsed: UInt256.fromJson(json["cumulativeGasUsed"], "cumulativeGasUsed"),
effectiveGasPrice: fromJson(?UInt256, json["effectiveGasPrice"], "effectiveGasPrice"),
gasUsed: UInt256.fromJson(json["gasUsed"], "gasUsed"),
contractAddress: fromJson(?Address, json["contractAddress"], "contractAddress"),
logs: seq[Log].fromJson(json["logs"], "logs"),
logsBloom: seq[byte].fromJson(json["logsBloom"], "logsBloom"),
transactionType: TransactionType.fromJson(json["type"], "type"),
status: TransactionStatus.fromJson(json["status"], "status")
)
# func fromJson*(json: JsonNode, result: var TransactionReceipt) =
# # Deserializes a transaction receipt, eg eth_getTransactionReceipt.
# # Spec: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
# json.expectFields "transactionHash", "transactionIndex", "cumulativeGasUsed",
# "effectiveGasPrice", "gasUsed", "logs", "logsBloom", "type",
# "status"
# result = TransactionReceipt(
# transactionHash: fromJson(TransactionHash, json["transactionHash"], "transactionHash"),
# transactionIndex: UInt256.fromJson(json["transactionIndex"], "transactionIndex"),
# blockHash: fromJson(?BlockHash, json["blockHash"], "blockHash"),
# blockNumber: fromJson(?UInt256, json["blockNumber"], "blockNumber"),
# sender: fromJson(?Address, json["from"], "from"),
# to: fromJson(?Address, json["to"], "to"),
# cumulativeGasUsed: UInt256.fromJson(json["cumulativeGasUsed"], "cumulativeGasUsed"),
# effectiveGasPrice: fromJson(?UInt256, json["effectiveGasPrice"], "effectiveGasPrice"),
# gasUsed: UInt256.fromJson(json["gasUsed"], "gasUsed"),
# contractAddress: fromJson(?Address, json["contractAddress"], "contractAddress"),
# logs: seq[Log].fromJson(json["logs"], "logs"),
# logsBloom: seq[byte].fromJson(json["logsBloom"], "logsBloom"),
# transactionType: TransactionType.fromJson(json["type"], "type"),
# status: TransactionStatus.fromJson(json["status"], "status")
# )
# func fromJson*[T](json: JsonNode, result: var Option[T]) =
# if json.isNil or json.kind == JNull:
# result = none T
# return
# var val: T
# fromJson(json, val)
# result = some val
# # if val =? T.fromJson(json):
# # result = some val
# # # result = none T
# func fromJson*[T](json: JsonNode, name = "", result: var seq[T]) =
# if json.kind != JArray:
# raiseSerializationError(fmt"Expected JArray to convert to seq, but got {json.kind}")
# for elem in json.elems:
# var v: T
# fromJson(elem, name, v)
# result.add(v)
# func fromJson*(T: type, json: JsonNode, name = ""): T =
# fromJson(json, name, result)
# func fromJson*(T: type, json: JsonNode, name = ""): T =
# fromJson(json, name, result)

View File

@ -0,0 +1,403 @@
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)