Home

Awesome

nim-json-serialization

License: Apache License: MIT Stability: experimental Github action

Flexible JSON serialization does not rely on run-time type information.

Overview

nim-json-serialization offers rich features on top of nim-serialization framework. The following is available but not an exhaustive list of features:

Spec compliance

nim-json-serialization implements RFC8259 JSON spec and pass these test suites:

Switchable features

Many of these switchable features are widely used features in various projects but are not standard JSON features. But you can access them using the flags:

Safety features

You can modify these default configurations to suit your needs.

Special types

Flavor

While flags and limits are runtime configuration, flavor is a powerful compile time mechanism to prevent cross contamination between different custom serializer operated the same type. For example, json-rpc subsystem dan json-rest subsystem maybe have different custom serializer for the same UInt256.

Json-Flavor will make sure, the compiler picks the right serializer for the right subsystem. You can use useDefaultSerializationIn to add serializers of a flavor to a specific type.

# These are the parameters you can pass to `createJsonFlavor` to create a new flavor.

  FlavorName: untyped
  mimeTypeValue = "application/json"
  automaticObjectSerialization = false
  requireAllFields = true
  omitOptionalFields = true
  allowUnknownFields = true
  skipNullFields = false
type
  OptionalFields = object
    one: Opt[string]
    two: Option[int]

createJsonFlavor OptJson
OptionalFields.useDefaultSerializationIn OptJson

omitOptionalFields is used by the Writer to ignore fields with null value. skipNullFields is used by the Reader to ignore fields with null value.

Decoder example

  type
    NimServer = object
      name: string
      port: int

    MixedServer = object
      name: JsonValueRef
      port: int

    StringServer = object
      name: JsonString
      port: JsonString

  # decode into native Nim
  var nim_native = Json.decode(rawJson, NimServer)

  # decode into mixed Nim + JsonValueRef
  var nim_mixed = Json.decode(rawJson, MixedServer)

  # decode any value into string
  var nim_string = Json.decode(rawJson, StringServer)

  # decode any valid JSON
  var json_value = Json.decode(rawJson, JsonValueRef)

Load and save

  var server = Json.loadFile("filename.json", Server)
  var server_string = Json.loadFile("filename.json", JsonString)

  Json.saveFile("filename.json", server)

Objects

Decoding an object can be achieved via the parseObject template. To parse the value, you can use one of the helper functions or use readValue. readObject and readObjectFields iterators are also handy when creating a custom object parser.

proc readValue*(r: var JsonReader, table: var Table[string, int]) =
  parseObject(r, key):
    table[key] = r.parseInt(int)

Sets and list-like

Similar to Object, sets and list or array-like data structures can be parsed using parseArray template. It comes in two variations, indexed and non-indexed.

Built-in readValue for regular seq and array is implemented for you. No built-in readValue for set or set-like is provided, you must overload it yourself depending on your need.

type
  HoldArray = object
    data: array[3, int]

  HoldSeq = object
    data: seq[int]

  WelderFlag = enum
    TIG
    MIG
    MMA

  Welder = object
    flags: set[WelderFlag]

proc readValue*(r: var JsonReader, value: var HoldArray) =
  # parseArray with index, `i` can be any valid identifier
  r.parseArray(i):
    value.data[i] = r.parseInt(int)

proc readValue*(r: var JsonReader, value: var HoldSeq) =
  # parseArray without index
  r.parseArray:
    let lastPos = value.data.len
    value.data.setLen(lastPos + 1)
    readValue(r, value.data[lastPos])

proc readValue*(r: var JsonReader, value: var Welder) =
  # populating set also okay
  r.parseArray:
    value.flags.incl r.parseInt(int).WelderFlag

Custom iterators

Using these custom iterators, you can have access to sub-token elements.

customIntValueIt(r: var JsonReader; body: untyped)
customNumberValueIt(r: var JsonReader; body: untyped)
customStringValueIt(r: var JsonReader; limit: untyped; body: untyped)
customStringValueIt(r: var JsonReader; body: untyped)

Convenience iterators

readArray(r: var JsonReader, ElemType: typedesc): ElemType
readObjectFields(r: var JsonReader, KeyType: type): KeyType
readObjectFields(r: var JsonReader): string
readObject(r: var JsonReader, KeyType: type, ValueType: type): (KeyType, ValueType)

Helper procs

When crafting a custom serializer, use these parsers, they are safe and intuitive. Avoid using the lexer directly.

tokKind(r: var JsonReader): JsonValueKind
parseString(r: var JsonReader, limit: int): string
parseString(r: var JsonReader): string
parseBool(r: var JsonReader): bool
parseNull(r: var JsonReader)
parseNumber(r: var JsonReader, T: type): JsonNumber[T: string or uint64]
parseNumber(r: var JsonReader, val: var JsonNumber)
toInt(r: var JsonReader, val: JsonNumber, T: type SomeInteger, portable: bool): T
parseInt(r: var JsonReader, T: type SomeInteger, portable: bool = false): T
toFloat(r: var JsonReader, val: JsonNumber, T: type SomeFloat): T
parseFloat(r: var JsonReader, T: type SomeFloat): T
parseAsString(r: var JsonReader, val: var string)
parseAsString(r: var JsonReader): JsonString
parseValue(r: var JsonReader, T: type): JsonValueRef[T: string or uint64]
parseValue(r: var JsonReader, val: var JsonValueRef)
parseArray(r: var JsonReader; body: untyped)
parseArray(r: var JsonReader; idx: untyped; body: untyped)
parseObject(r: var JsonReader, key: untyped, body: untyped)
parseObjectWithoutSkip(r: var JsonReader, key: untyped, body: untyped)
parseObjectSkipNullFields(r: var JsonReader, key: untyped, body: untyped)
parseObjectCustomKey(r: var JsonReader, keyAction: untyped, body: untyped)
parseJsonNode(r: var JsonReader): JsonNode
skipSingleJsValue(r: var JsonReader)
readRecordValue[T](r: var JsonReader, value: var T)

Helper procs of JsonWriter

beginRecord(w: var JsonWriter, T: type)
beginRecord(w: var JsonWriter)
endRecord(w: var JsonWriter)

writeObject(w: var JsonWriter, T: type)
writeObject(w: var JsonWriter)

writeFieldName(w: var JsonWriter, name: string)
writeField(w: var JsonWriter, name: string, value: auto)

iterator stepwiseArrayCreation[C](w: var JsonWriter, collection: C): auto
writeIterable(w: var JsonWriter, collection: auto)
writeArray[T](w: var JsonWriter, elements: openArray[T])

writeNumber[F,T](w: var JsonWriter[F], value: JsonNumber[T])
writeJsonValueRef[F,T](w: var JsonWriter[F], value: JsonValueRef[T])

Enums

type
  Fruit = enum
    Apple = "Apple"
    Banana = "Banana"
    
  Drawer = enum
    One
    Two
    
  Number = enum
    Three = 3
    Four = 4
    
  Mixed = enum
    Six = 6
    Seven = "Seven"

nim-json-serialization automatically detect which representation an enum should be parsed. The detection occurs when parse json literal and from the enum declaration itself. 'Fruit' expect string literal. 'Drawer' or 'Number' expect numeric literal. 'Mixed' is disallowed. If the json literal does not match the expected enum style, exception will be raised. But you can configure individual enum type with:

configureJsonDeserialization(
    T: type[enum], allowNumericRepr: static[bool] = false,
    stringNormalizer: static[proc(s: string): string] = strictNormalize)
    
# example:
Mixed.configureJsonDeserialization(allowNumericRepr = true) # only at top level

When encode an enum, user is also given flexibility to configure at Flavor level or for individual enum type.

type
  EnumRepresentation* = enum
    EnumAsString
    EnumAsNumber
    EnumAsStringifiedNumber

# examples:

# Flavor level
Json.flavorEnumRep(EnumAsString)   # default flavor, can be called from non top level
Flavor.flavorEnumRep(EnumAsNumber) # custom flavor, can be called from non top level

# individual enum type no matter what flavor
Fruit.configureJsonSerialization(EnumAsNumber) # only at top level

# individual enum type of specific flavor
MyJson.flavorEnumRep(Drawer, EnumAsString) # only at top level

License

Licensed and distributed under either of

or

at your option. These files may not be copied, modified, or distributed except according to those terms.