njson

2023-02-15

NJSON is a JSON handling framework with the focus on convenience and brevity.

Upstream URL

github.com/atlas-engineer/njson

Author

Atlas Engineer LLC

License

BSD-3 Clause
README

A JSON handling framework aiming for convenience and brevity.

NJSON aims to make it extremely convenient for you to decode and encode JSON data, in the minimum keystrokes/minutes possible.

1Getting started

Clone the Git repository:
  git clone --recursive https://github.com/atlas-engineer/njson ~/common-lisp/

Load NJSON (with a cl-json backend) in the REPL:

  ;; Show ASDF where NJSON is.
  (asdf:load-asd #p"/path/to/checkout/njson.asd")
  ;; Load it with ASDF.
  (asdf:load-system :njson/cl-json)
  ;; Alternatively, load it with Quicklisp.
  (ql:quickload :njson/cl-json)
  ;; NOTE: :NJSON system implicitly includes :NJSON/CL-JSON backend for
  ;; your convenience. If you want other backend, load :NJSON/BACKEND.

And start parsing right away, be it from file:

  (njson:decode #p"/path/to/njson/checkout/tests/test.json")
  ;; => (1 3.8 T NIL :UNDEFINED :NULL "foo" (1 2 3) ("bar" 8.3 T :NULL 1000000)
  ;;     #<HASH-TABLE :TEST EQUAL :COUNT 1 {100EAB1383}>
  ;;     #<HASH-TABLE :TEST EQUAL :COUNT 3 {100EAB16D3}>)

or from string:

  (njson:decode "[\"hello\", 5]")
  ;; => ("hello", 5)

or other specializeable types. Default methods support:

  • pathnames,
  • strings,
  • streams.

1.1Running tests

Given JSON backend-agnostic nature, you can only test every particular backend against the uniform set of tests that NJSON provides. So, to test CL-JSON, you can do:
  (asdf:test-system :njson/cl-json)

2What NJSON is not (and what it is, instead)

2.1NJSON is not a JSON parsing library.

It's one level higher: it's a convenience wrapper around your JSON parser of choice. NJSON is made in such a way so as to be usable with almost any JSON library out there. The default backend is CL-JSON, that proven itself extensible enough.

  • To make NJSON support your preferred JSON parser, you have tospecialize as little as two methods: decode-from-stream andencode-to-stream. If you care about correctness or proper typedispatching, you may also define (en|de)code-(to|from)-string and(en|de)code-(to|from)-file.

2.2NJSON is not propagating unnecessary dependencies on you.

The core (njson ASDF system) has no dependencies due to specifying only the generics to implement.

Every other dependency (CL-JSON in case of njson/cl-json backend) is optional and depends on which backend you want to use for parsing.

2.3NJSON is not the fastest JSON handling solution out there.

Plug-n-play usability and type variety are much higher of a priority than the performance. The types NJSON returns from its methods (and that your own methods extending NJSON should expect/return) are:

  • Lisp number-s for JSON numbers.
  • Lisp strings for JSON strings.
  • :null for JSON null.
  • t for true and nil for false.
  • :undefined for undefined.
  • Lists for JSON arrays.
  • Hash-tables for JSON objects.

With this basic (yet disjoint) set of types, you can easily typecase over NJSON output and make informed decisions about the JSON you have. Even if it's some couple of CPU work milliseconds slower than handling raw lists. It's faster in human work seconds, which are much more valuable.

2.4NJSON is not minimalist.

NJSON has strict requirements on the returned data, but this strictness enables a rich set of JSON-handling primitives/helpers. You can

  • safely :use #:njson in your packages if you want short and convenient JSONoperations there;
  • or you can define a package local nickname for :njson/aliases to bea mere j: (using trivial-package-local-nicknames), so that evenshorter helpers (just a couple of characters longer than the regularCL constructs) are available:
  (trivial-package-local-nicknames:add-package-local-nickname :j :njson/aliases :YOUR-PACKAGE)

The helpers are:

2.4.1FUNCTION njson:jget (alias: njson/aliases:get)

Gets the value from the JSON object/array indexed by a certain key

  (njson:jget "key" (njson:decode "{\"key\": 5}"))
  ;; => 5
  (njson:jget 5 (njson:decode "[0, 1, 2, 3, 4, 5]"))
  ;; => 5

2.4.2FUNCTION njson:jhas (alias: njson/aliases:has)

Checks the presence of the value under KEY-OR-INDEX in OBJECT.

  (njson:jhas "key" (njson:decode "{\"key\": 5}"))
  ;; => T
  (njson:jget 8 (njson:decode "[0, 1, 2, 3, 4, 5]"))
  ;; => NIL

2.4.3FUNCTION njson:jrem (alias: njson/aliases:rem)

Removes the value from the JSON object/array indexed by a certain key

  (defvar data (njson:decode "{\"key\": 5}"))
  data
  ;; => #<HASH-TABLE :TEST EQUAL :COUNT 1 {1007F2F1A3}>
  (njson:jrem "key" data)
  data
  ;; => #<HASH-TABLE :TEST EQUAL :COUNT 0 {1007F2F1A3}>

2.4.4FUNCTION njson:jcopy (alias: njson/aliases:copy)

Copies the whole thing it's passed, no mater the nesting, into a fresh new equal object.

  (njson:jget "key" (njson:decode "{\"key\": 5}"))
  ;; => 5
  (njson:jget 5 (njson:decode "[0, 1, 2, 3, 4, 5]"))
  ;; => 5

2.4.5FUNCTION njson:jtruep (aliases: njson:jtrue-p, njson:jtrue?, njson:truep, njson:true-p, njson:true?)

Checks whether the given value is true (in other words, neither false, nor null) per JSON standard.

All the macros below utilize it, so, if you want to change the behavior of those, specialize this function.

2.4.6MACRO njson:jwhen (alias: njson/aliases:when)

A regular CL when made aware of JSON's null and false.

  (njson:jwhen (njson:decode "null")
    "This is never returned.")
  ;; nil
  (njson:jwhen (njson:decode "5")
    "This is always returned.")
  ;; "This is always returned"

2.4.7MACRO njson:if (alias: njson/aliases:if)

A regular Lisp if aware of JSON truths and lies.

  (njson:jif (njson:decode "5")
             "This is always returned."
             "This is never returned.")
  ;; "This is always returned"

2.4.8MACRO njson:jor, njson:jand, njson:jnot (and aliases: njson/aliases:or, njson/aliases:and, njson/aliases:not)

Regular Lisp logic operators, with awareness of JSON values.

3Roadmap

  • Make an in-depth guide on supporting other backends.
  • Add an object-specific things, like j:keys and j:vals (would work for arrays too, I guess)?

Dependencies (2)

  • cl-json
  • nyxt

Dependents (1)

  • GitHub
  • Quicklisp