Compare commits

..

No commits in common. "master" and "v0.1.1" have entirely different histories.

21 changed files with 237 additions and 1129 deletions

View File

@ -1,41 +0,0 @@
name: changelog
on:
push:
branches:
- master
release:
types: [published]
jobs:
changelog:
name: Generate changelog
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Create Token
id: create_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
- name: "✏️ Generate release changelog"
uses: heinrichreimer/action-github-changelog-generator@v2.3
with:
token: ${{ steps.create_token.outputs.token }}
- name: Commit CHANGELOG.md
uses: planetscale/ghcommit-action@v0.1.33
with:
repo: ${{ github.repository }}
branch: master
commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'
file_pattern: CHANGELOG.md
env:
GITHUB_TOKEN: ${{ steps.create_token.outputs.token }}

View File

@ -1,36 +0,0 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
test:
name: Run tests
runs-on: ubuntu-latest
strategy:
matrix:
nim: [2.0.16, 2.2.4]
steps:
- 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: 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

3
.gitignore vendored
View File

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

View File

@ -1,96 +1,6 @@
# Changelog
## v0.1.1
Add `toJson(pretty=true)`, so that stringified json can be prettified 😊
Formatted all nim files using [`nph`](https://github.com/arnetheduck/nph)
## [Unreleased](https://github.com/logos-storage/nim-serde/tree/HEAD)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v.1.2.2...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))
## [v.1.2.2](https://github.com/logos-storage/nim-serde/tree/v.1.2.2) (2024-10-23)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.2.1...v.1.2.2)
**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.2.1](https://github.com/logos-storage/nim-serde/tree/v1.2.1) (2024-05-21)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.2.0...v1.2.1)
**Merged pull requests:**
- 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))
## [v1.2.0](https://github.com/logos-storage/nim-serde/tree/v1.2.0) (2024-05-14)
[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)
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
## v0.1.0
Initial versioned release

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):
@ -108,10 +108,7 @@ OptOut
Strict
```
Modes can be set in the `{.serialize.}` and/or `{.deserialize.}` pragmas on type
definitions. Each mode has a different meaning depending on if the type is being
serialized or deserialized. Modes can be set by setting `mode` in the `serialize` or
`deserialize` pragma annotation, eg:
Modes can be set in the `{.serialize.}` and/or `{.deserialize.}` pragmas on type definitions. Each mode has a different meaning depending on if the type is being serialized or deserialized. Modes can be set by setting `mode` in the `serialize` or `deserialize` pragma annotation, eg:
```nim
type MyType {.serialize(mode=Strict).} = object
@ -129,11 +126,7 @@ type MyType {.serialize(mode=Strict).} = object
## Default modes
`nim-serde` will de/serialize types if they are not annotated with `serialize` or
`deserialize`, but will assume a default mode. By default, with no pragmas specified,
`serde` will always serialize in `OptIn` mode, meaning any fields to b Additionally, if
the types are annotated, but a mode is not specified, `serde` will assume a (possibly
different) default mode.
`nim-serde` will de/serialize types if they are not annotated with `serialize` or `deserialize`, but will assume a default mode. By default, with no pragmas specified, `serde` will always serialize in `OptIn` mode, meaning any fields to b Additionally, if the types are annotated, but a mode is not specified, `serde` will assume a (possibly different) default mode.
```nim
# Type is not annotated
@ -159,8 +152,7 @@ type MyObj {.serialize, deserialize.} = object
| Default (pragma, but no mode) | `OptOut` | `OptOut` |
## Serde field options
Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties
can be set on these pragmas, determining de/serialization behavior.
Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties can be set on these pragmas, determining de/serialization behavior.
For example,
@ -200,9 +192,7 @@ assert !Person.fromJson(createResponse) == Person(id: 1)
```
### `key`
Specifying a `key`, will alias the field name. When seriazlizing, json will be written
with `key` instead of the field name. When deserializing, the json must contain `key`
for the field to be deserialized.
Specifying a `key`, will alias the field name. When seriazlizing, json will be written with `key` instead of the field name. When deserializing, the json must contain `key` for the field to be deserialized.
### `ignore`
Specifying `ignore`, will prevent de/serialization on the field.
@ -217,8 +207,7 @@ Specifying `ignore`, will prevent de/serialization on the field.
## Deserialization
`serde` deserializes using `fromJson`, and in all instances returns `Result[T,
CatchableError]`, where `T` is the type being deserialized. For example:
`serde` deserializes using `fromJson`, and in all instances returns `Result[T, CatchableError]`, where `T` is the type being deserialized. For example:
```nim
type MyType = object
@ -254,31 +243,9 @@ assert res.error of SerdeError
assert res.error.msg == "json field(s) missing in object: {\"extra\"}"
```
## Custom types
If `serde` can't de/serialize a custom type, de/serialization can be supported by
overloading `%` and `fromJson`. For example:
```nim
type
Address* = distinct array[20, byte]
SerializationError* = object of CatchableError
func `%`*(address: Address): JsonNode =
%($address)
func fromJson(_: type Address, json: JsonNode): ?!Address =
expectJsonKind(Address, JString, json)
without address =? Address.init(json.getStr), error:
return failure newException(SerializationError,
"Failed to convert '" & $json & "' to Address: " & error.msg)
success address
```
## Serializing to string (`toJson`)
`toJson` is a shortcut for serializing an object into its serialized string
representation:
`toJson` is a shortcut for serializing an object into its serialized string representation:
```nim
import pkg/serde/json
@ -301,11 +268,9 @@ return RestApiResponse.response(availability.toJson,
## `std/json` drop-in replacment
`nim-serde` can be used as a drop-in replacement for the [standard library's `json`
module](https://nim-lang.org/docs/json.html), with a few notable improvements.
`nim-serde` can be used as a drop-in replacement for the [standard library's `json` module](https://nim-lang.org/docs/json.html), with a few notable improvements.
Instead of importing `std/json` into your application, `pkg/serde/json` can be imported
instead:
Instead of importing `std/json` into your application, `pkg/serde/json` can be imported instead:
```diff
- import std/json
@ -330,8 +295,7 @@ expected["hello"] = newJString("world")
assert %*{"hello": "world"} == expected
```
As well, serialization of types can be overridden, and serialization of custom types can
be introduced. Here, we are overriding the serialization of `int`:
As well, serialization of types can be overridden, and serialization of custom types can be introduced. Here, we are overriding the serialization of `int`:
```nim
import pkg/serde/json
@ -344,8 +308,7 @@ assert 1.toJson == "2"
## `parseJson` and exception tracking
Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception
tracking breaks, eg
Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception tracking breaks, eg
```nim
@ -368,10 +331,7 @@ proc parseMe(me: string): JsonNode =
assert """{"hello":"world"}""".parseMe == %* { "hello": "world" }
```
This is due to `std/json`'s `parseJson` incorrectly raising `Exception`. This can be
worked around by instead importing `serde` and calling its `JsonNode.parse` routine.
Note that `serde`'s `JsonNode.parse` returns a `Result[JsonNode, CatchableError]`
instead of just a plain `JsonNode` object as in `std/json`'s `parseJson`:
This is due to `std/json`'s `parseJson` incorrectly raising `Exception`. This can be worked around by instead importing `serde` and calling its `parseJson`. Note that `serde`'s `parseJson` returns a `Result[JsonNode, CatchableError]` instead of just a plain `JsonNode` object:
```nim
import pkg/serde/json
@ -382,31 +342,9 @@ type
MyAppError = object of CatchableError
proc parseMe(me: string): JsonNode {.raises: [MyAppError].} =
without parsed =? JsonNode.parse(me), error:
without parsed =? me.parseJson, error:
raise newException(MyAppError, error.msg)
parsed
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 = "0.1.1"
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)
@ -133,35 +126,22 @@ proc fromJson*[N: static[int], T: array[N, byte]](_: type T, json: JsonNode): ?!
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)
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 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 +187,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 +196,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 +214,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,111 +222,20 @@ 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
success(res)
proc fromJson*(_: type JsonNode, json: string): ?!JsonNode =
return JsonNode.parse(json)
proc fromJson*[T: ref object or object](_: type T, bytes: openArray[byte]): ?!T =
let json = string.fromBytes(bytes)
proc fromJson*[T: ref object or object](_: type T, bytes: seq[byte]): ?!T =
let json = ?parse(string.fromBytes(bytes))
T.fromJson(json)
proc fromJson*[T: ref object or object](_: type T, json: string): ?!T =
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
proc fromJson*(_: type JsonNode, jsn: string): ?!JsonNode =
return parser.parseJson(jsn)
proc fromJson*[T: ref object or object](_: type T, jsn: string): ?!T =
let jsn = ?parser.parseJson(jsn) # 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
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,17 +2,13 @@ import std/json as stdjson
import pkg/questionable/results
import ./errors
import ./types
{.push raises: [].}
proc parse*(_: type JsonNode, json: string): ?!JsonNode =
# Used as a replacement for `std/json.parseJson`. Will not raise Exception like in the
# standard library
proc parseJson*(json: string): ?!JsonNode =
## fix for nim raising Exception
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)
return failure newException(JsonParseError, e.msg)

View File

@ -21,30 +21,30 @@ export types
{.push raises: [].}
logScope:
topics = "nimserde json serializer"
topics = "json serialization"
proc `%`*(s: string): JsonNode =
func `%`*(s: string): JsonNode =
newJString(s)
proc `%`*(n: uint): JsonNode =
func `%`*(n: uint): JsonNode =
if n > cast[uint](int.high):
newJString($n)
else:
newJInt(BiggestInt(n))
proc `%`*(n: int): JsonNode =
func `%`*(n: int): JsonNode =
newJInt(n)
proc `%`*(n: BiggestUInt): JsonNode =
func `%`*(n: BiggestUInt): JsonNode =
if n > cast[BiggestUInt](BiggestInt.high):
newJString($n)
else:
newJInt(BiggestInt(n))
proc `%`*(n: BiggestInt): JsonNode =
func `%`*(n: BiggestInt): JsonNode =
newJInt(n)
proc `%`*(n: float): JsonNode =
func `%`*(n: float): JsonNode =
if n != n:
newJString("nan")
elif n == Inf:
@ -54,10 +54,10 @@ proc `%`*(n: float): JsonNode =
else:
newJFloat(n)
proc `%`*(b: bool): JsonNode =
func `%`*(b: bool): JsonNode =
newJBool(b)
proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
func `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
if keyVals.len == 0:
return newJArray()
let jObj = newJObject()
@ -68,13 +68,13 @@ proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
template `%`*(j: JsonNode): JsonNode =
j
proc `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode =
func `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode =
let jObj = newJObject()
for k, v in table:
jObj[k] = ? %v
jObj
proc `%`*[T](opt: Option[T]): JsonNode =
func `%`*[T](opt: Option[T]): JsonNode =
if opt.isSome:
%(opt.get)
else:
@ -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"
@ -129,22 +125,22 @@ proc `%`*[T: object or ref object](obj: T): JsonNode =
proc `%`*(o: enum): JsonNode =
% $o
proc `%`*(stint: StInt | StUint): JsonNode =
func `%`*(stint: StInt | StUint): JsonNode =
%stint.toString
proc `%`*(cstr: cstring): JsonNode =
func `%`*(cstr: cstring): JsonNode =
% $cstr
proc `%`*(arr: openArray[byte]): JsonNode =
func `%`*(arr: openArray[byte]): JsonNode =
%arr.to0xHex
proc `%`*[T](elements: openArray[T]): JsonNode =
func `%`*[T](elements: openArray[T]): JsonNode =
let jObj = newJArray()
for elem in elements:
jObj.add(%elem)
jObj
proc `%`*[T: distinct](id: T): JsonNode =
func `%`*[T: distinct](id: T): JsonNode =
type baseType = T.distinctBase
%baseType(id)

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,139 @@
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/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 from non-hex string representation":
let json = newJString("100000")
check !UInt256.fromJson(json) == 100000.u256
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 = !"[1,2,3]".parseJson
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 =
!"""{
"mystring": "abc",
"myint": 123,
"myoption": true
}""".parseJson
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 =
!"""{
"mystring": "abc",
"mybool": true
}""".parseJson
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 =
!"""{
"mystring": "abc",
"mybool": true,
"extra": "extra"
}""".parseJson
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 =
!"""{
"mystring": "abc"
}""".parseJson
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 =
!"""{
"mystring": "abc",
"myint": 1
}""".parseJson
let deserialized = !MyRef.fromJson(json)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint

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"