Json in Nim

import json, streams

type
  Response1 = object
    page: int
    fruits: seq[string]

  Response2 = object
    page {.jsonKey: "page".}: int
    fruits {.jsonKey: "fruits".}: seq[string]

proc main() =
  # First we'll look at encoding basic data types to
  # JSON strings. Here are some examples for atomic
  # values.
  echo %* true
  echo %* 1
  echo %* 2.34
  echo %* "gopher"

  # And here are some for slices and maps, which encode
  # to JSON arrays and objects as you'd expect.
  let slcD = @["apple", "peach", "pear"]
  echo %* slcD

  let mapD = {"apple": 5, "lettuce": 7}.toTable
  echo %* mapD

  # The JSON module can automatically encode your
  # custom data types. It will only include exported
  # fields in the encoded output and will by default
  # use those names as the JSON keys.
  let res1D = Response1(page: 1, fruits: @["apple", "peach", "pear"])
  echo %* res1D

  # You can use pragma annotations on object field declarations
  # to customize the encoded JSON key names. Check the
  # definition of `Response2` above to see an example
  # of such annotations.
  let res2D = Response2(page: 1, fruits: @["apple", "peach", "pear"])
  echo %* res2D

  # Now let's look at decoding JSON data into Nim
  # values. Here's an example for a generic data
  # structure.
  let byt = """{"num":6.13,"strs":["a","b"]}"""

  # We need to provide a variable where the JSON
  # module can put the decoded data. This
  # `JsonNode` will hold a tree of JSON data.
  let dat = parseJson(byt)
  echo dat

  # In order to use the values in the decoded JSON,
  # we'll need to access them using the appropriate methods.
  # For example here we get the value in `num` as a float.
  let num = dat["num"].getFloat()
  echo num

  # Accessing nested data requires a series of
  # method calls.
  let str1 = dat["strs"][0].getStr()
  echo str1

  # We can also decode JSON into custom data types.
  # This has the advantages of adding additional
  # type-safety to our programs and eliminating the
  # need for type assertions when accessing the decoded
  # data.
  let str = """{"page": 1, "fruits": ["apple", "peach"]}"""
  var res = to(parseJson(str), Response2)
  echo res
  echo res.fruits[0]

  # In the examples above we always used strings as
  # intermediates between the data and JSON representation.
  # We can also stream JSON encodings directly to streams
  # like files or network connections.
  let d = {"apple": 5, "lettuce": 7}.toTable
  let stream = newFileStream(stdout)
  stream.write($(%* d))
  stream.close()

main()

This Nim code demonstrates JSON encoding and decoding, mirroring the functionality of the original Go example. Here’s a brief explanation of the key differences and Nim-specific features:

  1. Nim uses the json module for JSON operations.
  2. Custom types are defined using object instead of struct.
  3. JSON key customization is done using pragmas like {.jsonKey: "page".} instead of struct tags.
  4. Nim uses %* for JSON encoding and parseJson for decoding.
  5. Nim’s JsonNode is used as a generic JSON representation, similar to Go’s map[string]interface{}.
  6. Nim’s to proc is used for decoding JSON into custom types.
  7. Nim uses newFileStream for creating a stream to stdout, similar to Go’s json.NewEncoder(os.Stdout).

This code provides a comprehensive overview of JSON handling in Nim, covering encoding, decoding, and working with custom types, while maintaining the structure and explanations from the original Go example.