Compare commits

..

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

21 changed files with 236 additions and 1107 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 nimble.paths
.idea .idea
vendor/ vendor/
.vscode/ .vscode/
nimbledeps

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) ## v0.1.0
Initial versioned release
[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)*

View File

@ -1,4 +1,4 @@
Copyright (c) 2025 Logos Storage Copyright (c) 2024 Codex Storage
Licensed and distributed under either of Licensed and distributed under either of
[MIT license](http://opensource.org/licenses/MIT) or [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) 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): Serialize all fields of a type (OptOut mode):
@ -108,10 +108,7 @@ OptOut
Strict Strict
``` ```
Modes can be set in the `{.serialize.}` and/or `{.deserialize.}` pragmas on type 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:
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 ```nim
type MyType {.serialize(mode=Strict).} = object type MyType {.serialize(mode=Strict).} = object
@ -129,11 +126,7 @@ type MyType {.serialize(mode=Strict).} = object
## Default modes ## Default modes
`nim-serde` will de/serialize types if they are not annotated with `serialize` or `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.
`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 ```nim
# Type is not annotated # Type is not annotated
@ -159,8 +152,7 @@ type MyObj {.serialize, deserialize.} = object
| Default (pragma, but no mode) | `OptOut` | `OptOut` | | Default (pragma, but no mode) | `OptOut` | `OptOut` |
## Serde field options ## Serde field options
Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties can be set on these pragmas, determining de/serialization behavior.
can be set on these pragmas, determining de/serialization behavior.
For example, For example,
@ -200,9 +192,7 @@ assert !Person.fromJson(createResponse) == Person(id: 1)
``` ```
### `key` ### `key`
Specifying a `key`, will alias the field name. When seriazlizing, json will be written 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.
with `key` instead of the field name. When deserializing, the json must contain `key`
for the field to be deserialized.
### `ignore` ### `ignore`
Specifying `ignore`, will prevent de/serialization on the field. Specifying `ignore`, will prevent de/serialization on the field.
@ -217,8 +207,7 @@ Specifying `ignore`, will prevent de/serialization on the field.
## Deserialization ## Deserialization
`serde` deserializes using `fromJson`, and in all instances returns `Result[T, `serde` deserializes using `fromJson`, and in all instances returns `Result[T, CatchableError]`, where `T` is the type being deserialized. For example:
CatchableError]`, where `T` is the type being deserialized. For example:
```nim ```nim
type MyType = object type MyType = object
@ -277,8 +266,7 @@ func fromJson(_: type Address, json: JsonNode): ?!Address =
## Serializing to string (`toJson`) ## Serializing to string (`toJson`)
`toJson` is a shortcut for serializing an object into its serialized string `toJson` is a shortcut for serializing an object into its serialized string representation:
representation:
```nim ```nim
import pkg/serde/json import pkg/serde/json
@ -301,11 +289,9 @@ return RestApiResponse.response(availability.toJson,
## `std/json` drop-in replacment ## `std/json` drop-in replacment
`nim-serde` can be used as a drop-in replacement for the [standard library's `json` `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.
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 of importing `std/json` into your application, `pkg/serde/json` can be imported instead:
instead:
```diff ```diff
- import std/json - import std/json
@ -330,8 +316,7 @@ expected["hello"] = newJString("world")
assert %*{"hello": "world"} == expected assert %*{"hello": "world"} == expected
``` ```
As well, serialization of types can be overridden, and serialization of custom types can As well, serialization of types can be overridden, and serialization of custom types can be introduced. Here, we are overriding the serialization of `int`:
be introduced. Here, we are overriding the serialization of `int`:
```nim ```nim
import pkg/serde/json import pkg/serde/json
@ -344,8 +329,7 @@ assert 1.toJson == "2"
## `parseJson` and exception tracking ## `parseJson` and exception tracking
Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception tracking breaks, eg
tracking breaks, eg
```nim ```nim
@ -368,10 +352,7 @@ proc parseMe(me: string): JsonNode =
assert """{"hello":"world"}""".parseMe == %* { "hello": "world" } assert """{"hello":"world"}""".parseMe == %* { "hello": "world" }
``` ```
This is due to `std/json`'s `parseJson` incorrectly raising `Exception`. This can be 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:
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`:
```nim ```nim
import pkg/serde/json import pkg/serde/json
@ -382,31 +363,9 @@ type
MyAppError = object of CatchableError MyAppError = object of CatchableError
proc parseMe(me: string): JsonNode {.raises: [MyAppError].} = proc parseMe(me: string): JsonNode {.raises: [MyAppError].} =
without parsed =? JsonNode.parse(me), error: without parsed =? me.parseJson, error:
raise newException(MyAppError, error.msg) raise newException(MyAppError, error.msg)
parsed parsed
assert """{"hello":"world"}""".parseMe == %* { "hello": "world" } 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:usages
--styleCheck:error --styleCheck:error
# begin Nimble config (version 2) # begin Nimble config (version 1)
--noNimblePath when fileExists("nimble.paths"):
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths" include "nimble.paths"
# end Nimble config # 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 # Package
version = "1.2.2" version = "0.1.2"
author = "nim-serde authors" author = "nim-serde authors"
description = "Easy-to-use serialization capabilities (currently json only)." description = "Easy-to-use serialization capabilities (currently json only), with a drop-in replacement for std/json."
license = "MIT" license = "MIT"
skipDirs = @["tests"] skipDirs = @["tests"]
# Dependencies # 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 "questionable >= 0.10.13 & < 0.11.0"
requires "stint" requires "stint"
requires "stew" 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 ./stdjson
import ./pragmas import ./pragmas
import ./types import ./types
import ./helpers
export parser export parser
export chronicles except toJson export chronicles except toJson
@ -29,7 +28,7 @@ export types
{.push raises: [].} {.push raises: [].}
logScope: logScope:
topics = "nimserde json deserializer" topics = "json deserialization"
template expectJsonKind( template expectJsonKind(
expectedType: type, expectedKinds: set[JsonNodeKind], json: JsonNode 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) expectJsonKind(T, {JInt, JString}, json)
case json.kind case json.kind
of JString: 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: without x =? parseBiggestUInt(json.str).catch, error:
return failure newSerdeError(error.msg) return failure newSerdeError(error.msg)
return success cast[T](x) 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 = proc fromJson*[T: distinct](_: type T, json: JsonNode): ?!T =
success T(?T.distinctBase.fromJson(json)) success T(?T.distinctBase.fromJson(json))
proc fromJson*(T: typedesc[StUint or StInt], json: JsonNode): ?!T = proc fromJson*[N: static[int], T: StUint[N]](_: type T, json: JsonNode): ?!T =
expectJsonKind(T, {JString, JInt}, json) expectJsonKind(T, JString, json)
let jsonStr = json.getStr
case json.kind let prefix = jsonStr[0 .. 1].toLowerAscii
of JInt: case prefix
return catch parse($json, T) of "0x":
else: # JString (only other kind allowed) catch parse(jsonStr, T, 16)
if json.isNullString: of "0o":
let err = newSerdeError("Cannot deserialize 'null' into type " & $T) catch parse(jsonStr, T, 8)
return failure(err) of "0b":
catch parse(jsonStr, T, 2)
let jsonStr = json.getStr else:
let prefix = catch parse(jsonStr, T)
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] = 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) return success(none T)
without val =? T.fromJson(json), error: without val =? T.fromJson(json), error:
return failure(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 let isOptionalValue = typeof(value) is Option
var skip = false # workaround for 'continue' not supported in a 'fields' loop 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 case mode
of Strict: of Strict:
if opts.key notin json: 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" warn "object field marked as 'ignore' while in Strict mode, field will be deserialized anyway"
of OptIn: of OptIn:
if not hasDeserializePragma: if not hasDeserializePragma:
trace "object field not marked as 'deserialize', skipping" debug "object field not marked as 'deserialize', skipping"
skip = true skip = true
elif opts.ignore: elif opts.ignore:
trace "object field marked as 'ignore', skipping" debug "object field marked as 'ignore', skipping"
skip = true skip = true
elif opts.key notin json and not isOptionalValue: elif opts.key notin json and not isOptionalValue:
return failure newSerdeError("object field missing in json: " & opts.key) return failure newSerdeError("object field missing in json: " & opts.key)
of OptOut: of OptOut:
if opts.ignore: 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 skip = true
elif hasDeserializePragma and opts.key == name: elif hasDeserializePragma and opts.key == name:
warn "object field marked as deserialize in OptOut mode, but 'ignore' not set, field will be deserialized" 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: if isOptionalValue:
let jsonVal = json{opts.key} let jsonVal = json{opts.key}
without parsed =? typeof(value).fromJson(jsonVal), e: 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 `type` = $typeof(value), json = jsonVal, error = e.msg
return failure(e) return failure(e)
value = parsed value = parsed
@ -246,111 +222,20 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
# not Option[T] # not Option[T]
elif opts.key in json and jsonVal =? json{opts.key}.catch and not jsonVal.isNil: elif opts.key in json and jsonVal =? json{opts.key}.catch and not jsonVal.isNil:
without parsed =? typeof(value).fromJson(jsonVal), e: 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 `type` = $typeof(value), json = jsonVal, error = e.msg
return failure(e) return failure(e)
value = parsed value = parsed
success(res) success(res)
proc fromJson*(_: type JsonNode, json: string): ?!JsonNode = proc fromJson*[T: ref object or object](_: type T, bytes: seq[byte]): ?!T =
return JsonNode.parse(json) let json = ?parse(string.fromBytes(bytes))
proc fromJson*[T: ref object or object](_: type T, bytes: openArray[byte]): ?!T =
let json = string.fromBytes(bytes)
T.fromJson(json) T.fromJson(json)
proc fromJson*[T: ref object or object](_: type T, json: string): ?!T = proc fromJson*(_: type JsonNode, jsn: string): ?!JsonNode =
let jsn = ?JsonNode.parse(json) # full qualification required in-module only 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) 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 pkg/questionable/results
import ./errors
import ./types import ./types
{.push raises: [].} {.push raises: [].}
proc parse*(_: type JsonNode, json: string): ?!JsonNode = proc parseJson*(json: string): ?!JsonNode =
# Used as a replacement for `std/json.parseJson`. Will not raise Exception like in the ## fix for nim raising Exception
# standard library
try: try:
without val =? stdjson.parseJson(json).catch, error: return stdjson.parseJson(json).catch
return failure error.mapErrTo(JsonParseError)
return success val
except Exception as e: except Exception as e:
return failure newException(JsonParseError, e.msg, e) return failure newException(JsonParseError, e.msg, e)

View File

@ -21,30 +21,30 @@ export types
{.push raises: [].} {.push raises: [].}
logScope: logScope:
topics = "nimserde json serializer" topics = "json serialization"
proc `%`*(s: string): JsonNode = func `%`*(s: string): JsonNode =
newJString(s) newJString(s)
proc `%`*(n: uint): JsonNode = func `%`*(n: uint): JsonNode =
if n > cast[uint](int.high): if n > cast[uint](int.high):
newJString($n) newJString($n)
else: else:
newJInt(BiggestInt(n)) newJInt(BiggestInt(n))
proc `%`*(n: int): JsonNode = func `%`*(n: int): JsonNode =
newJInt(n) newJInt(n)
proc `%`*(n: BiggestUInt): JsonNode = func `%`*(n: BiggestUInt): JsonNode =
if n > cast[BiggestUInt](BiggestInt.high): if n > cast[BiggestUInt](BiggestInt.high):
newJString($n) newJString($n)
else: else:
newJInt(BiggestInt(n)) newJInt(BiggestInt(n))
proc `%`*(n: BiggestInt): JsonNode = func `%`*(n: BiggestInt): JsonNode =
newJInt(n) newJInt(n)
proc `%`*(n: float): JsonNode = func `%`*(n: float): JsonNode =
if n != n: if n != n:
newJString("nan") newJString("nan")
elif n == Inf: elif n == Inf:
@ -54,10 +54,10 @@ proc `%`*(n: float): JsonNode =
else: else:
newJFloat(n) newJFloat(n)
proc `%`*(b: bool): JsonNode = func `%`*(b: bool): JsonNode =
newJBool(b) newJBool(b)
proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode = func `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
if keyVals.len == 0: if keyVals.len == 0:
return newJArray() return newJArray()
let jObj = newJObject() let jObj = newJObject()
@ -68,13 +68,13 @@ proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
template `%`*(j: JsonNode): JsonNode = template `%`*(j: JsonNode): JsonNode =
j j
proc `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode = func `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode =
let jObj = newJObject() let jObj = newJObject()
for k, v in table: for k, v in table:
jObj[k] = ? %v jObj[k] = ? %v
jObj jObj
proc `%`*[T](opt: Option[T]): JsonNode = func `%`*[T](opt: Option[T]): JsonNode =
if opt.isSome: if opt.isSome:
%(opt.get) %(opt.get)
else: else:
@ -99,20 +99,16 @@ proc `%`*[T: object or ref object](obj: T): JsonNode =
let hasSerialize = value.hasCustomPragma(serialize) let hasSerialize = value.hasCustomPragma(serialize)
var skip = false # workaround for 'continue' not supported in a 'fields' loop 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 case mode
of OptIn: of OptIn:
if not hasSerialize: if not hasSerialize:
trace "object field not marked with serialize, skipping" debug "object field not marked with serialize, skipping"
skip = true skip = true
elif opts.ignore: elif opts.ignore:
skip = true skip = true
of OptOut: of OptOut:
if opts.ignore: 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 skip = true
elif hasSerialize and opts.key == name: # all serialize params are default 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" 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 = proc `%`*(o: enum): JsonNode =
% $o % $o
proc `%`*(stint: StInt | StUint): JsonNode = func `%`*(stint: StInt | StUint): JsonNode =
%stint.toString %stint.toString
proc `%`*(cstr: cstring): JsonNode = func `%`*(cstr: cstring): JsonNode =
% $cstr % $cstr
proc `%`*(arr: openArray[byte]): JsonNode = func `%`*(arr: openArray[byte]): JsonNode =
%arr.to0xHex %arr.to0xHex
proc `%`*[T](elements: openArray[T]): JsonNode = func `%`*[T](elements: openArray[T]): JsonNode =
let jObj = newJArray() let jObj = newJArray()
for elem in elements: for elem in elements:
jObj.add(%elem) jObj.add(%elem)
jObj jObj
proc `%`*[T: distinct](id: T): JsonNode = func `%`*[T: distinct](id: T): JsonNode =
type baseType = T.distinctBase type baseType = T.distinctBase
%baseType(id) %baseType(id)

View File

@ -1,4 +1,5 @@
import std/strutils import std/strutils
func flatten*(s: string): string = 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 std/math
import ./deserialize/std import std/options
import ./deserialize/stint 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/testSerialize
import ./json/testSerializeModes 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"