Compare commits

..

No commits in common. "master" and "v1.1.0" have entirely different histories.

21 changed files with 329 additions and 967 deletions

View File

@ -4,8 +4,6 @@ on:
push:
branches:
- master
release:
types: [published]
jobs:

View File

@ -12,25 +12,17 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
nim: [2.0.16, 2.2.4]
nim: [1.6.16, stable]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v4
- name: Install Nim
uses: iffy/install-nim@v4
with:
version: ${{ matrix.nim }}
- name: Build
run: nimble install -y
- name: Install Nim
uses: iffy/install-nim@v4
with:
version: ${{ matrix.nim }}
- name: Build
run: nimble install -y
- name: Test
run: nimble test -y
status:
if: always()
needs: [test]
runs-on: ubuntu-latest
steps:
- if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
run: exit 1
- name: Test
run: nimble test -y

3
.gitignore vendored
View File

@ -5,5 +5,4 @@ nimble.develop
nimble.paths
.idea
vendor/
.vscode/
nimbledeps
.vscode/

View File

@ -1,95 +1,40 @@
# Changelog
## [Unreleased](https://github.com/logos-storage/nim-serde/tree/HEAD)
## [Unreleased](https://github.com/codex-storage/nim-serde/tree/HEAD)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v.1.2.2...HEAD)
[Full Changelog](https://github.com/codex-storage/nim-serde/compare/v1.0.0...HEAD)
**Merged pull requests:**
- chore: rename Codex to Logos Storage [\#34](https://github.com/logos-storage/nim-serde/pull/34) ([2-towns](https://github.com/2-towns))
- Update chronicles [\#33](https://github.com/logos-storage/nim-serde/pull/33) ([markspanbroek](https://github.com/markspanbroek))
- fix loglevel and add log scope to allow filtering and avoid log pollution [\#30](https://github.com/logos-storage/nim-serde/pull/30) ([dryajov](https://github.com/dryajov))
- ci: add matrix status job [\#29](https://github.com/logos-storage/nim-serde/pull/29) ([veaceslavdoina](https://github.com/veaceslavdoina))
- chore: v1.1.0 [\#11](https://github.com/codex-storage/nim-serde/pull/11) ([emizzle](https://github.com/emizzle))
- deserialize non-prefixed stuint [\#10](https://github.com/codex-storage/nim-serde/pull/10) ([emizzle](https://github.com/emizzle))
- deserialize seq\[T\] and Option\[T\] from string [\#9](https://github.com/codex-storage/nim-serde/pull/9) ([emizzle](https://github.com/emizzle))
## [v.1.2.2](https://github.com/logos-storage/nim-serde/tree/v.1.2.2) (2024-10-23)
## [v1.0.0](https://github.com/codex-storage/nim-serde/tree/v1.0.0) (2024-02-13)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.2.1...v.1.2.2)
[Full Changelog](https://github.com/codex-storage/nim-serde/compare/v0.1.2...v1.0.0)
**Merged pull requests:**
- v1.2.2 [\#28](https://github.com/logos-storage/nim-serde/pull/28) ([emizzle](https://github.com/emizzle))
- chore: bumps nim from 1.6.16 to 1.6.20 in ci [\#27](https://github.com/logos-storage/nim-serde/pull/27) ([emizzle](https://github.com/emizzle))
- chore: remove unneeded echos [\#26](https://github.com/logos-storage/nim-serde/pull/26) ([emizzle](https://github.com/emizzle))
- v1.0.0 [\#8](https://github.com/codex-storage/nim-serde/pull/8) ([emizzle](https://github.com/emizzle))
- fix: change serializer funcs to procs [\#7](https://github.com/codex-storage/nim-serde/pull/7) ([emizzle](https://github.com/emizzle))
- Use token for changelog generator [\#6](https://github.com/codex-storage/nim-serde/pull/6) ([veaceslavdoina](https://github.com/veaceslavdoina))
- Adjust workflows for changelog generation [\#5](https://github.com/codex-storage/nim-serde/pull/5) ([veaceslavdoina](https://github.com/veaceslavdoina))
- Change parseJson to JsonNode.parse [\#4](https://github.com/codex-storage/nim-serde/pull/4) ([emizzle](https://github.com/emizzle))
- Add CI workflow [\#3](https://github.com/codex-storage/nim-serde/pull/3) ([emizzle](https://github.com/emizzle))
- Fix deserialization of openArray\[byte\] [\#2](https://github.com/codex-storage/nim-serde/pull/2) ([emizzle](https://github.com/emizzle))
## [v1.2.1](https://github.com/logos-storage/nim-serde/tree/v1.2.1) (2024-05-21)
## [v0.1.2](https://github.com/codex-storage/nim-serde/tree/v0.1.2) (2024-02-08)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.2.0...v1.2.1)
[Full Changelog](https://github.com/codex-storage/nim-serde/compare/v0.1.1...v0.1.2)
**Merged pull requests:**
## [v0.1.1](https://github.com/codex-storage/nim-serde/tree/v0.1.1) (2024-02-07)
- v1.2.1 [\#25](https://github.com/logos-storage/nim-serde/pull/25) ([emizzle](https://github.com/emizzle))
- fix: force symbol resolution for types that serde de/serializes [\#24](https://github.com/logos-storage/nim-serde/pull/24) ([emizzle](https://github.com/emizzle))
- feat: improve deserialization from string [\#23](https://github.com/logos-storage/nim-serde/pull/23) ([emizzle](https://github.com/emizzle))
- feat: improve stint parsing [\#22](https://github.com/logos-storage/nim-serde/pull/22) ([emizzle](https://github.com/emizzle))
[Full Changelog](https://github.com/codex-storage/nim-serde/compare/v0.1.0...v0.1.1)
## [v1.2.0](https://github.com/logos-storage/nim-serde/tree/v1.2.0) (2024-05-14)
## [v0.1.0](https://github.com/codex-storage/nim-serde/tree/v0.1.0) (2024-02-07)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.1.1...v1.2.0)
**Merged pull requests:**
- chore: v1.2.0 [\#21](https://github.com/logos-storage/nim-serde/pull/21) ([emizzle](https://github.com/emizzle))
- fix: add missing test update [\#20](https://github.com/logos-storage/nim-serde/pull/20) ([emizzle](https://github.com/emizzle))
- chore: reorganize deserialize tests [\#19](https://github.com/logos-storage/nim-serde/pull/19) ([emizzle](https://github.com/emizzle))
- fix: UInt256 not correctly deserializing from string [\#18](https://github.com/logos-storage/nim-serde/pull/18) ([emizzle](https://github.com/emizzle))
## [v1.1.1](https://github.com/logos-storage/nim-serde/tree/v1.1.1) (2024-05-13)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.1.0...v1.1.1)
**Merged pull requests:**
- chore: v.1.1.1 [\#17](https://github.com/logos-storage/nim-serde/pull/17) ([emizzle](https://github.com/emizzle))
- chore\[formatting\]: update formatting [\#16](https://github.com/logos-storage/nim-serde/pull/16) ([emizzle](https://github.com/emizzle))
- add empty string test for UInt256 [\#15](https://github.com/logos-storage/nim-serde/pull/15) ([emizzle](https://github.com/emizzle))
- Fix log topics [\#14](https://github.com/logos-storage/nim-serde/pull/14) ([benbierens](https://github.com/benbierens))
- run changelog workflow on release [\#12](https://github.com/logos-storage/nim-serde/pull/12) ([emizzle](https://github.com/emizzle))
## [v1.1.0](https://github.com/logos-storage/nim-serde/tree/v1.1.0) (2024-02-14)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.0.0...v1.1.0)
**Merged pull requests:**
- chore: v1.1.0 [\#11](https://github.com/logos-storage/nim-serde/pull/11) ([emizzle](https://github.com/emizzle))
- deserialize non-prefixed stuint [\#10](https://github.com/logos-storage/nim-serde/pull/10) ([emizzle](https://github.com/emizzle))
- deserialize seq\[T\] and Option\[T\] from string [\#9](https://github.com/logos-storage/nim-serde/pull/9) ([emizzle](https://github.com/emizzle))
## [v1.0.0](https://github.com/logos-storage/nim-serde/tree/v1.0.0) (2024-02-13)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v0.1.2...v1.0.0)
**Merged pull requests:**
- v1.0.0 [\#8](https://github.com/logos-storage/nim-serde/pull/8) ([emizzle](https://github.com/emizzle))
- fix: change serializer funcs to procs [\#7](https://github.com/logos-storage/nim-serde/pull/7) ([emizzle](https://github.com/emizzle))
- Use token for changelog generator [\#6](https://github.com/logos-storage/nim-serde/pull/6) ([veaceslavdoina](https://github.com/veaceslavdoina))
- Adjust workflows for changelog generation [\#5](https://github.com/logos-storage/nim-serde/pull/5) ([veaceslavdoina](https://github.com/veaceslavdoina))
- Change parseJson to JsonNode.parse [\#4](https://github.com/logos-storage/nim-serde/pull/4) ([emizzle](https://github.com/emizzle))
- Add CI workflow [\#3](https://github.com/logos-storage/nim-serde/pull/3) ([emizzle](https://github.com/emizzle))
- Fix deserialization of openArray\[byte\] [\#2](https://github.com/logos-storage/nim-serde/pull/2) ([emizzle](https://github.com/emizzle))
## [v0.1.2](https://github.com/logos-storage/nim-serde/tree/v0.1.2) (2024-02-08)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v0.1.1...v0.1.2)
## [v0.1.1](https://github.com/logos-storage/nim-serde/tree/v0.1.1) (2024-02-07)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v0.1.0...v0.1.1)
## [v0.1.0](https://github.com/logos-storage/nim-serde/tree/v0.1.0) (2024-02-07)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/5a8e85449d9320d2277bc9aadf1daae61c7f057b...v0.1.0)
[Full Changelog](https://github.com/codex-storage/nim-serde/compare/5a8e85449d9320d2277bc9aadf1daae61c7f057b...v0.1.0)

View File

@ -1,4 +1,4 @@
Copyright (c) 2025 Logos Storage
Copyright (c) 2024 Codex Storage
Licensed and distributed under either of
[MIT license](http://opensource.org/licenses/MIT) or

View File

@ -44,7 +44,7 @@ let jsn2 = """{
}"""
assert !MyType2.fromJson(jsn2) == MyType2(field1: false, field2: true)
# Note, the ! operator is part of https://github.com/logos-storage/questionable, which retrieves a value if set
# Note, the ! operator is part of https://github.com/codex-storage/questionable, which retrieves a value if set
```
Serialize all fields of a type (OptOut mode):
@ -388,25 +388,3 @@ proc parseMe(me: string): JsonNode {.raises: [MyAppError].} =
assert """{"hello":"world"}""".parseMe == %* { "hello": "world" }
```
## Known issues
There is a known issue when using mixins with generic overloaded procs like
`fromJson`. At the time of mixin call, only the `fromJson` overloads in scope of
the called mixin are available to be dispatched at runtime. There could be other
`fromJson` overloads declared in other modules, but are not in scope at the time
the mixin was called. Therefore, anytime `fromJson` is called targeting a
declared overload, it may or may not be dispatchable. This can be worked around
by forcing the `fromJson` overload into scope at compile time. For example, in
your application where the `fromJson` overload is defined, at the bottom of the
module add:
```nim
static: MyType.fromJson("")
```
This will ensure that the `MyType.fromJson` overload is dispatchable.
The basic types that serde supports should already have their overloads forced
in scope in [the `deserializer`
module](./serde/json/deserializer.nim#L340-L356).
For an illustration of the problem, please see this [narrow example](https://github.com/gmega/serialization-bug/tree/main/narrow) by [@gmega](https://github.com/gmega).

View File

@ -1,8 +1,7 @@
--styleCheck:usages
--styleCheck:error
# begin Nimble config (version 2)
--noNimblePath
when withDir(thisDir(), system.fileExists("nimble.paths")):
# begin Nimble config (version 1)
when fileExists("nimble.paths"):
include "nimble.paths"
# end Nimble config

View File

@ -1,130 +0,0 @@
{
"version": 2,
"packages": {
"results": {
"version": "0.5.1",
"vcsRevision": "df8113dda4c2d74d460a8fa98252b0b771bf1f27",
"url": "https://github.com/arnetheduck/nim-results",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "a9c011f74bc9ed5c91103917b9f382b12e82a9e7"
}
},
"unittest2": {
"version": "0.2.4",
"vcsRevision": "8b51e99b4a57fcfb31689230e75595f024543024",
"url": "https://github.com/status-im/nim-unittest2",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "746106a4dfefffce497f1693733f1c1513b5c62c"
}
},
"stew": {
"version": "0.4.1",
"vcsRevision": "e5740014961438610d336cd81706582dbf2c96f0",
"url": "https://github.com/status-im/nim-stew",
"downloadMethod": "git",
"dependencies": [
"results",
"unittest2"
],
"checksums": {
"sha1": "996d9c058ee078d0209a5f539424a0235683918c"
}
},
"faststreams": {
"version": "0.4.1",
"vcsRevision": "c3ac3f639ed1d62f59d3077d376a29c63ac9750c",
"url": "https://github.com/status-im/nim-faststreams",
"downloadMethod": "git",
"dependencies": [
"stew",
"unittest2"
],
"checksums": {
"sha1": "e2d03280cf0675314136e6e63d9d928d9dfc9705"
}
},
"serialization": {
"version": "0.4.9",
"vcsRevision": "b5193007a49639b21b2b80cd8ef8cbe0df6b0e48",
"url": "https://github.com/status-im/nim-serialization",
"downloadMethod": "git",
"dependencies": [
"faststreams",
"unittest2",
"stew"
],
"checksums": {
"sha1": "ab0812a7e16ae39ee68f4a6b1938a1b83dee34be"
}
},
"json_serialization": {
"version": "0.4.2",
"vcsRevision": "e076cb9a2dff30e13aad0bb4a13049da86d07967",
"url": "https://github.com/status-im/nim-json-serialization",
"downloadMethod": "git",
"dependencies": [
"serialization",
"stew",
"results"
],
"checksums": {
"sha1": "2b26a9e0fc79638dbb9272fb4ab5a1d79264f938"
}
},
"testutils": {
"version": "0.8.0",
"vcsRevision": "e4d37dc1652d5c63afb89907efb5a5e812261797",
"url": "https://github.com/status-im/nim-testutils",
"downloadMethod": "git",
"dependencies": [
"unittest2"
],
"checksums": {
"sha1": "d1678f50aa47d113b4e77d41eec2190830b523fa"
}
},
"chronicles": {
"version": "0.12.2",
"vcsRevision": "27ec507429a4eb81edc20f28292ee8ec420be05b",
"url": "https://github.com/status-im/nim-chronicles",
"downloadMethod": "git",
"dependencies": [
"faststreams",
"serialization",
"json_serialization",
"testutils"
],
"checksums": {
"sha1": "02febb20d088120b2836d3306cfa21f434f88f65"
}
},
"questionable": {
"version": "0.10.15",
"vcsRevision": "82d90b67bcfb7f2e918b61dace2ff1a4ced60935",
"url": "https://github.com/logos-storage/questionable",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "3238ff637c7b44d2fa8fcb839a8ded968e389de3"
}
},
"stint": {
"version": "0.8.2",
"vcsRevision": "470b7892561b5179ab20bd389a69217d6213fe58",
"url": "https://github.com/status-im/nim-stint",
"downloadMethod": "git",
"dependencies": [
"stew",
"unittest2"
],
"checksums": {
"sha1": "d8f871fd617e7857192d4609fe003b48942a8ae5"
}
}
},
"tasks": {}
}

View File

@ -1,14 +1,20 @@
# Package
version = "1.2.2"
author = "nim-serde authors"
description = "Easy-to-use serialization capabilities (currently json only)."
license = "MIT"
skipDirs = @["tests"]
version = "1.1.0"
author = "nim-serde authors"
description = "Easy-to-use serialization capabilities (currently json only), with a drop-in replacement for std/json."
license = "MIT"
skipDirs = @["tests"]
# Dependencies
requires "chronicles >= 0.10.3"
requires "nim >= 1.6.14"
requires "chronicles >= 0.10.3 & < 0.11.0"
requires "questionable >= 0.10.13 & < 0.11.0"
requires "stint"
requires "stew"
requires "asynctest >= 0.5.1 & < 0.6.0"
task test, "Run the test suite":
exec "nimble install -d -y"
withDir "tests":
exec "nimble test"

View File

@ -16,7 +16,6 @@ import ./errors
import ./stdjson
import ./pragmas
import ./types
import ./helpers
export parser
export chronicles except toJson
@ -29,7 +28,7 @@ export types
{.push raises: [].}
logScope:
topics = "nimserde json deserializer"
topics = "json deserialization"
template expectJsonKind(
expectedType: type, expectedKinds: set[JsonNodeKind], json: JsonNode
@ -82,12 +81,6 @@ proc fromJson*[T: SomeInteger](_: type T, json: JsonNode): ?!T =
expectJsonKind(T, {JInt, JString}, json)
case json.kind
of JString:
if json.isNullString:
let err = newSerdeError("Cannot deserialize 'null' into type " & $T)
return failure(err)
elif json.isEmptyString:
return success T(0)
without x =? parseBiggestUInt(json.str).catch, error:
return failure newSerdeError(error.msg)
return success cast[T](x)
@ -134,34 +127,22 @@ proc fromJson*[T: distinct](_: type T, json: JsonNode): ?!T =
success T(?T.distinctBase.fromJson(json))
proc fromJson*(T: typedesc[StUint or StInt], json: JsonNode): ?!T =
expectJsonKind(T, {JString, JInt}, json)
case json.kind
of JInt:
return catch parse($json, T)
else: # JString (only other kind allowed)
if json.isNullString:
let err = newSerdeError("Cannot deserialize 'null' into type " & $T)
return failure(err)
let jsonStr = json.getStr
let prefix =
if jsonStr.len >= 2:
jsonStr[0 .. 1].toLowerAscii
else:
jsonStr
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)
expectJsonKind(T, JString, json)
let jsonStr = json.getStr
let prefix = if jsonStr.len >= 2: jsonStr[0 .. 1].toLowerAscii
else: jsonStr
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 or json.isEmptyString or json.isNullString:
if json.isNil or json.kind == JNull:
return success(none T)
without val =? T.fromJson(json), error:
return failure(error)
@ -207,10 +188,6 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
let isOptionalValue = typeof(value) is Option
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 deserialization"
case mode
of Strict:
if opts.key notin json:
@ -220,16 +197,16 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
warn "object field marked as 'ignore' while in Strict mode, field will be deserialized anyway"
of OptIn:
if not hasDeserializePragma:
trace "object field not marked as 'deserialize', skipping"
debug "object field not marked as 'deserialize', skipping"
skip = true
elif opts.ignore:
trace "object field marked as 'ignore', skipping"
debug "object field marked as 'ignore', skipping"
skip = true
elif opts.key notin json and not isOptionalValue:
return failure newSerdeError("object field missing in json: " & opts.key)
of OptOut:
if opts.ignore:
trace "object field is opted out of deserialization ('ignore' is set), skipping"
debug "object field is opted out of deserialization ('ignore' is set), skipping"
skip = true
elif hasDeserializePragma and opts.key == name:
warn "object field marked as deserialize in OptOut mode, but 'ignore' not set, field will be deserialized"
@ -238,7 +215,7 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
if isOptionalValue:
let jsonVal = json{opts.key}
without parsed =? typeof(value).fromJson(jsonVal), e:
trace "failed to deserialize field",
debug "failed to deserialize field",
`type` = $typeof(value), json = jsonVal, error = e.msg
return failure(e)
value = parsed
@ -246,7 +223,7 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
# not Option[T]
elif opts.key in json and jsonVal =? json{opts.key}.catch and not jsonVal.isNil:
without parsed =? typeof(value).fromJson(jsonVal), e:
trace "failed to deserialize field",
debug "failed to deserialize field",
`type` = $typeof(value), json = jsonVal, error = e.msg
return failure(e)
value = parsed
@ -264,93 +241,10 @@ proc fromJson*[T: ref object or object](_: type T, json: string): ?!T =
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
T.fromJson(jsn)
proc fromJson*[T: enum](_: type T, json: string): ?!T =
T.fromJson(newJString(json))
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool](
_: type T, json: string
): ?!T =
if json == "" or json == "null":
let err = newSerdeError("Cannot deserialize '' or 'null' into type " & $T)
failure err
else:
let jsn = ?JsonNode.parse(json)
T.fromJson(jsn)
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
_: type Option[T], json: string
): ?!Option[T] =
if json == "" or json == "null":
success T.none
else:
when T is enum:
let jsn = newJString(json)
else:
let jsn = ?JsonNode.parse(json)
Option[T].fromJson(jsn)
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
_: type seq[T], json: string
): ?!seq[T] =
if json == "" or json == "null":
success newSeq[T]()
else:
if T is enum:
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome")
return failure err
let jsn = ?JsonNode.parse(json)
seq[T].fromJson(jsn)
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
_: type ?seq[T], json: string
): ?!Option[seq[T]] =
if json == "" or json == "null":
success seq[T].none
else:
if T is enum:
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome")
return failure err
let jsn = ?JsonNode.parse(json)
Option[seq[T]].fromJson(jsn)
proc fromJson*[T: ref object or object](_: type seq[T], json: string): ?!seq[T] =
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
seq[T].fromJson(jsn)
proc fromJson*(T: typedesc[StUint or StInt], json: string): ?!T =
T.fromJson(newJString(json))
proc fromJson*[T: ref object or object](_: type ?T, json: string): ?!Option[T] =
when T is (StUint or StInt):
let jsn = newJString(json)
else:
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
Option[T].fromJson(jsn)
# Force symbols into scope when mixins are used with generic overloads. When
# mixins are used with generic overloads, the overloaded symbols in scope of the
# mixin are evaluated from the perspective of the mixin. This creates issues in
# downstream modules that may inadvertantly dispatch *only* to the symbols in
# the scope of the mixin, even when the module with the wrong symbol overloads
# is not imported. By forcing the compiler to use symbols for types handled by
# serde, we can be sure that these symbols are available to downstream modules.
# We can also be sure that these `fromJson` symbols can be overloaded where
# needed.
static:
discard bool.fromJson("")
discard Option[bool].fromJson("")
discard seq[bool].fromJson("")
discard uint.fromJson("")
discard Option[uint].fromJson("")
discard seq[uint].fromJson("")
discard int.fromJson("")
discard Option[int].fromJson("")
discard seq[int].fromJson("")
discard UInt256.fromJson("")
discard Option[UInt256].fromJson("")
discard seq[UInt256].fromJson("")
discard JsonNode.fromJson("")
type DistinctType = distinct int
discard DistinctType.fromJson(newJString(""))
discard UInt256.fromJson(newSeq[byte](0))

View File

@ -1,7 +0,0 @@
import std/json
func isEmptyString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == ""
func isNullString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == "null"

View File

@ -2,7 +2,6 @@ import std/json as stdjson
import pkg/questionable/results
import ./errors
import ./types
{.push raises: [].}
@ -11,8 +10,6 @@ proc parse*(_: type JsonNode, json: string): ?!JsonNode =
# Used as a replacement for `std/json.parseJson`. Will not raise Exception like in the
# standard library
try:
without val =? stdjson.parseJson(json).catch, error:
return failure error.mapErrTo(JsonParseError)
return success val
return stdjson.parseJson(json).catch
except Exception as e:
return failure newException(JsonParseError, e.msg, e)

View File

@ -21,7 +21,7 @@ export types
{.push raises: [].}
logScope:
topics = "nimserde json serializer"
topics = "json serialization"
proc `%`*(s: string): JsonNode =
newJString(s)
@ -99,20 +99,16 @@ proc `%`*[T: object or ref object](obj: T): JsonNode =
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"
debug "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"
debug "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"

View File

@ -1,4 +1,5 @@
import std/strutils
func flatten*(s: string): string =
s.replace(" ").replace("\n")
s.replace(" ")
.replace("\n")

View File

@ -1,7 +0,0 @@
import ./json/testDeserialize
import ./json/testDeserializeModes
import ./json/testPragmas
import ./json/testSerialize
import ./json/testSerializeModes
{.warning[UnusedImport]: off.}

View File

@ -1,209 +0,0 @@
import std/unittest
import pkg/serde
import pkg/questionable
import pkg/questionable/results
import pkg/stew/byteutils
import pkg/stint
suite "json - deserialize objects":
test "can deserialize json objects":
type MyObj = object
mystring: string
myint: int
myoption: ?bool
let expected = MyObj(mystring: "abc", myint: 123, myoption: some true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"myint": 123,
"myoption": true
}"""
)
check !MyObj.fromJson(json) == expected
test "ignores serialize pragma when deserializing":
type MyObj = object
mystring {.serialize.}: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"mybool": true
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes objects with extra fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"mybool": true,
"extra": "extra"
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes objects with less fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: false)
let json =
!JsonNode.parse(
"""{
"mystring": "abc"
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes ref objects":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"myint": 1
}"""
)
let deserialized = !MyRef.fromJson(json)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
test "deserializes openArray[byte]":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let byteArray = """{
"mystring": "abc",
"myint": 1
}""".toBytes
let deserialized = !MyRef.fromJson(byteArray)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
suite "json - deserialize objects from string":
test "deserializes objects from string":
type MyObj = object
mystring: string
myint: int
let expected = MyObj(mystring: "abc", myint: 1)
let myObjJson =
"""{
"mystring": "abc",
"myint": 1
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes ref objects from string":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let myRefJson =
"""{
"mystring": "abc",
"myint": 1
}"""
let deserialized = !MyRef.fromJson(myRefJson)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
test "deserializes seq of objects from string":
type MyObj = object
mystring: string
myint: int
let expected = @[MyObj(mystring: "abc", myint: 1)]
let myObjsJson =
"""[{
"mystring": "abc",
"myint": 1
}]"""
check !seq[MyObj].fromJson(myObjsJson) == expected
test "deserializes Option of object from string":
type MyObj = object
mystring: string
myint: int
let expected = some MyObj(mystring: "abc", myint: 1)
let myObjJson =
"""{
"mystring": "abc",
"myint": 1
}"""
check !(Option[MyObj].fromJson(myObjJson)) == expected
test "deserializes object with UInt256 from string":
type MyObj = object
mystring: string
myu256: UInt256
let expected = MyObj(mystring: "abc", myu256: 1.u256)
let myObjJson =
"""{
"mystring": "abc",
"myu256": 1
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes object with stringified UInt256 from string":
type MyObj = object
mystring: string
myu256: UInt256
let expected = MyObj(mystring: "abc", myu256: 1.u256)
let myObjJson =
"""{
"mystring": "abc",
"myu256": "1"
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes object with ?UInt256 from string":
type MyObj = object
mystring: string
myu256: ?UInt256
let expected = MyObj(mystring: "abc", myu256: UInt256.none)
let myObjJson =
"""{
"mystring": "abc",
}"""
check !MyObj.fromJson(myObjJson) == expected

View File

@ -1,217 +0,0 @@
import std/math
import std/unittest
import pkg/serde
import pkg/questionable
import pkg/questionable/results
suite "json - deserialize std types":
test "deserializes NaN float":
check %NaN == newJString("nan")
test "deserialize enum":
type MyEnum = enum
First
Second
let json = newJString("Second")
check !MyEnum.fromJson(json) == Second
test "deserializes Option[T] when has a value":
let json = newJInt(1)
check (!fromJson(?int, json) == some 1)
test "deserializes Option[T] when has a string value":
check (!fromJson(?int, "1") == some 1)
test "deserializes Option[T] from empty string":
check (!fromJson(?int, "") == int.none)
test "deserializes Option[T] from empty string":
check (!fromJson(?int, "") == int.none)
test "cannot deserialize T from null string":
let res = fromJson(int, "null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type int"
test "deserializes Option[T] when doesn't have a value":
let json = newJNull()
check !fromJson(?int, json) == none int
test "deserializes float":
let json = newJFloat(1.234)
check !float.fromJson(json) == 1.234
test "deserializes float from string":
check !float.fromJson("1.234") == 1.234
test "cannot deserialize float from empty string":
let res = float.fromJson("")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type float"
test "cannot deserialize float from null string":
let res = float.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type float"
test "deserializes Inf float":
let json = newJString("inf")
check !float.fromJson(json) == Inf
test "deserializes -Inf float":
let json = newJString("-inf")
check !float.fromJson(json) == -Inf
test "deserializes NaN float":
let json = newJString("nan")
check (!float.fromJson(json)).isNaN
test "deserializes array to sequence":
let expected = @[1, 2, 3]
let json = !JsonNode.parse("[1,2,3]")
check !seq[int].fromJson(json) == expected
test "deserializes uints int.high or smaller":
let largeUInt: uint = uint(int.high)
let json = newJInt(BiggestInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "deserializes large uints":
let largeUInt: uint = uint(int.high) + 1'u
let json = newJString($BiggestUInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "deserializes bool from JBool":
let json = newJBool(true)
check !bool.fromJson(json)
test "deserializes bool from string":
check !bool.fromJson("true")
test "cannot deserialize bool from empty string":
let res = bool.fromJson("")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type bool"
test "cannot deserialize bool from null string":
let res = bool.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type bool"
test "deserializes ?bool from string":
check Option[bool].fromJson("true") == success true.some
test "deserializes ?bool from empty string":
check !Option[bool].fromJson("") == bool.none
test "deserializes ?bool from null string":
check !Option[bool].fromJson("null") == bool.none
test "deserializes seq[bool] from JArray":
let json = newJArray()
json.add(newJBool(true))
json.add(newJBool(false))
check !seq[bool].fromJson(json) == @[true, false]
test "deserializes seq[bool] from string":
check !seq[bool].fromJson("[true, false]") == @[true, false]
test "deserializes seq[bool] from empty string":
check !seq[bool].fromJson("") == newSeq[bool]()
test "deserializes seq[bool] from null string":
check !seq[bool].fromJson("null") == newSeq[bool]()
test "cannot deserialize seq[bool] from unknown string":
let res = seq[bool].fromJson("blah")
check res.error of JsonParseError
check res.error.msg == "input(1, 4) Error: { expected"
test "deserializes ?seq[bool] from string":
check Option[seq[bool]].fromJson("[true, false]") == success @[true, false].some
test "deserializes ?seq[bool] from empty string":
check !Option[seq[bool]].fromJson("") == seq[bool].none
test "deserializes ?seq[bool] from null string":
check !Option[seq[bool]].fromJson("null") == seq[bool].none
test "deserializes enum from JString":
type MyEnum = enum
one
let json = newJString("one")
check !MyEnum.fromJson(json) == MyEnum.one
test "deserializes enum from string":
type MyEnum = enum
one
check !MyEnum.fromJson("one") == MyEnum.one
test "cannot deserialize enum from empty string":
type MyEnum = enum
one
let res = MyEnum.fromJson("")
check res.error of SerdeError
check res.error.msg == "Invalid enum value: "
test "cannot deserialize enum from null string":
type MyEnum = enum
one
let res = MyEnum.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Invalid enum value: null"
test "deserializes ?enum from string":
type MyEnum = enum
one
check Option[MyEnum].fromJson("one") == success MyEnum.one.some
test "deserializes ?enum from empty string":
type MyEnum = enum
one
check !Option[MyEnum].fromJson("") == MyEnum.none
test "deserializes ?enum from null string":
type MyEnum = enum
one
check !Option[MyEnum].fromJson("null") == MyEnum.none
test "deserializes seq[enum] from string":
type MyEnum = enum
one
two
let res = seq[MyEnum].fromJson("[one,two]")
check res.error of SerdeError
check res.error.msg ==
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
test "deserializes ?seq[enum] from string":
type MyEnum = enum
one
two
let res = Option[seq[MyEnum]].fromJson("[one,two]")
check res.error of SerdeError
check res.error.msg ==
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
test "deserializes ?seq[MyEnum] from empty string":
type MyEnum = enum
one
check !Option[seq[MyEnum]].fromJson("") == seq[MyEnum].none
test "deserializes ?seq[MyEnum] from null string":
type MyEnum = enum
one
check !Option[seq[MyEnum]].fromJson("null") == seq[MyEnum].none

View File

@ -1,122 +0,0 @@
import std/options
import std/unittest
import pkg/stint
import pkg/serde
import pkg/questionable
import pkg/questionable/results
suite "json - deserialize stint":
test "deserializes UInt256 from an empty JString":
let json = newJString("")
check !UInt256.fromJson(json) == 0.u256
test "deserializes UInt256 from an empty string":
check !UInt256.fromJson("") == 0.u256
test "deserializes UInt256 from null string":
let res = UInt256.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize 'null' into type UInt256"
test "deserializes UInt256 from JNull":
let res = UInt256.fromJson(newJNull())
check res.error of UnexpectedKindError
check res.error.msg ==
"deserialization to UInt256 failed: expected {JInt, JString} but got JNull"
test "deserializes ?UInt256 from an empty JString":
let json = newJString("")
check !Option[UInt256].fromJson(json) == UInt256.none
test "deserializes ?UInt256 from an empty string":
check !Option[UInt256].fromJson("") == UInt256.none
test "deserializes ?UInt256 from null string":
check !Option[UInt256].fromJson("null") == UInt256.none
test "deserializes ?UInt256 from JNull":
check !Option[UInt256].fromJson(newJNull()) == UInt256.none
test "deserializes seq[UInt256] from string":
check seq[UInt256].fromJson("[1,2,3]") == success @[1.u256, 2.u256, 3.u256]
test "deserializes seq[UInt256] from string with empty string item":
check seq[UInt256].fromJson("[1,2,\"\"]") == success @[1.u256, 2.u256, 0.u256]
test "deserializes seq[UInt256] from string with null item":
let res = seq[UInt256].fromJson("[1,2,null]")
check res.error of UnexpectedKindError
check res.error.msg ==
"deserialization to UInt256 failed: expected {JInt, JString} but got JNull"
test "deserializes seq[UInt256] from string with null string item":
let res = seq[UInt256].fromJson("[1,2,\"null\"]")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize 'null' into type UInt256"
test "deserializes seq[?UInt256] from string":
check seq[?UInt256].fromJson("[1,2,3]") ==
success @[1.u256.some, 2.u256.some, 3.u256.some]
test "deserializes seq[?UInt256] from string with empty string item":
check seq[?UInt256].fromJson("[1,2,\"\"]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes seq[?UInt256] from string with null item":
check seq[?UInt256].fromJson("[1,2,null]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes seq[?UInt256] from string with null string item":
check seq[?UInt256].fromJson("[1,2,\"null\"]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes UInt256 from JString with no prefix":
let json = newJString("1")
check !UInt256.fromJson(json) == 1.u256
test "deserializes ?UInt256 from JString with no prefix":
let json = newJString("1")
check !Option[UInt256].fromJson(json) == 1.u256.some
test "deserializes UInt256 from string with no prefix":
check !UInt256.fromJson("1") == 1.u256
test "deserializes ?UInt256 from string with no prefix":
check !Option[UInt256].fromJson("1") == 1.u256.some
test "deserializes UInt256 from hex JString representation":
let json = newJString("0x1")
check !UInt256.fromJson(json) == 0x1.u256
test "deserializes ?UInt256 from hex JString representation":
let json = newJString("0x1")
check !Option[UInt256].fromJson(json) == 0x1.u256.some
test "deserializes ?UInt256 from hex string representation":
check !Option[UInt256].fromJson("0x1") == 0x1.u256.some
test "deserializes UInt256 from octal JString representation":
let json = newJString("0o1")
check !UInt256.fromJson(json) == 0o1.u256
test "deserializes ?UInt256 from octal JString representation":
let json = newJString("0o1")
check !Option[UInt256].fromJson(json) == 0o1.u256.some
test "deserializes ?UInt256 from octal string representation":
check !Option[UInt256].fromJson("0o1") == 0o1.u256.some
test "deserializes UInt256 from binary JString representation":
let json = newJString("0b1")
check !UInt256.fromJson(json) == 0b1.u256
test "deserializes ?UInt256 from binary JString representation":
let json = newJString("0b1")
check !Option[UInt256].fromJson(json) == 0b1.u256.some
test "deserializes ?UInt256 from binary string representation":
check !Option[UInt256].fromJson("0b1") == 0b1.u256.some
test "deserializes Int256 with no prefix":
let json = newJString("1")
check !Int256.fromJson(json) == 1.i256

View File

@ -1,3 +1,241 @@
import ./deserialize/objects
import ./deserialize/std
import ./deserialize/stint
import std/math
import std/options
import std/unittest
import pkg/stint
import pkg/serde
import pkg/stew/byteutils
import pkg/questionable
import pkg/questionable/results
suite "json serialization - deserialize":
test "deserializes NaN float":
check %NaN == newJString("nan")
test "deserialize enum":
type MyEnum = enum
First
Second
let json = newJString("Second")
check !MyEnum.fromJson(json) == Second
test "deserializes UInt256 with no prefix":
let json = newJString("1")
check !UInt256.fromJson(json) == 1.u256
test "deserializes UInt256 from hex string representation":
let json = newJString("0x1")
check !UInt256.fromJson(json) == 0x1.u256
test "deserializes UInt256 from octal string representation":
let json = newJString("0o1")
check !UInt256.fromJson(json) == 0o1.u256
test "deserializes UInt256 from binary string representation":
let json = newJString("0b1")
check !UInt256.fromJson(json) == 0b1.u256
test "deserializes UInt256 from non-hex string representation":
let json = newJString("100000")
check !UInt256.fromJson(json) == 100000.u256
test "deserializes Int256 with no prefix":
let json = newJString("1")
check !Int256.fromJson(json) == 1.i256
test "deserializes Option[T] when has a value":
let json = newJInt(1)
check (!fromJson(?int, json) == some 1)
test "deserializes Option[T] when doesn't have a value":
let json = newJNull()
check !fromJson(?int, json) == none int
test "deserializes float":
let json = newJFloat(1.234)
check !float.fromJson(json) == 1.234
test "deserializes Inf float":
let json = newJString("inf")
check !float.fromJson(json) == Inf
test "deserializes -Inf float":
let json = newJString("-inf")
check !float.fromJson(json) == -Inf
test "deserializes NaN float":
let json = newJString("nan")
check (!float.fromJson(json)).isNaN
test "deserializes array to sequence":
let expected = @[1, 2, 3]
let json = !JsonNode.parse("[1,2,3]")
check !seq[int].fromJson(json) == expected
test "deserializes uints int.high or smaller":
let largeUInt: uint = uint(int.high)
let json = newJInt(BiggestInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "deserializes large uints":
let largeUInt: uint = uint(int.high) + 1'u
let json = newJString($BiggestUInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "can deserialize json objects":
type MyObj = object
mystring: string
myint: int
myoption: ?bool
let expected = MyObj(mystring: "abc", myint: 123, myoption: some true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"myint": 123,
"myoption": true
}"""
)
check !MyObj.fromJson(json) == expected
test "ignores serialize pragma when deserializing":
type MyObj = object
mystring {.serialize.}: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"mybool": true
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes objects with extra fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"mybool": true,
"extra": "extra"
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes objects with less fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: false)
let json =
!JsonNode.parse(
"""{
"mystring": "abc"
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes ref objects":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"myint": 1
}"""
)
let deserialized = !MyRef.fromJson(json)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
test "deserializes openArray[byte]":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let byteArray = """{
"mystring": "abc",
"myint": 1
}""".toBytes
let deserialized = !MyRef.fromJson(byteArray)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
suite "deserialize from string":
test "deserializes objects from string":
type MyObj = object
mystring: string
myint: int
let expected = MyObj(mystring: "abc", myint: 1)
let myObjJson = """{
"mystring": "abc",
"myint": 1
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes ref objects from string":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let myRefJson = """{
"mystring": "abc",
"myint": 1
}"""
let deserialized = !MyRef.fromJson(myRefJson)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
test "deserializes seq[T] from string":
type MyObj = object
mystring: string
myint: int
let expected = @[MyObj(mystring: "abc", myint: 1)]
let myObjsJson = """[{
"mystring": "abc",
"myint": 1
}]"""
check !seq[MyObj].fromJson(myObjsJson) == expected
test "deserializes Option[T] from string":
type MyObj = object
mystring: string
myint: int
let expected = some MyObj(mystring: "abc", myint: 1)
let myObjJson = """{
"mystring": "abc",
"myint": 1
}"""
check !(Option[MyObj].fromJson(myObjJson)) == expected

View File

@ -4,4 +4,4 @@ import ./json/testPragmas
import ./json/testSerialize
import ./json/testSerializeModes
{.warning[UnusedImport]: off.}
{.warning[UnusedImport]:off.}

11
tests/test.nimble Normal file
View File

@ -0,0 +1,11 @@
version = "0.1.0"
author = "nim serde authors"
description = "tests for nim serde library"
license = "MIT"
requires "asynctest >= 0.5.1 & < 0.6.0"
requires "questionable >= 0.10.13 & < 0.11.0"
task test, "Run the test suite":
exec "nimble install -d -y"
exec "nim c -r test"