herodotus

2022-04-01

Wrapper around Yason JSON parser/encoder with convenience methods for CLOS

Upstream URL

github.com/HenryS1/herodotus

Author

Henry Steere

Maintainer

henry.steere@gmail.com

License

BSD, MIT
README
https://github.com/HenryS1/herodotus/actions/workflows/ci.yaml/badge.svg

1Herodotus

1.1Installation

The library is available on quicklisp since the 2021-04-11 dist so you can install with

(ql:quickload :herodotus)

To install from source clone the repo into your lisp sources directory and load with asdf

(asdf:load-system :herodotus)
or use qlot
git herodotus https://github.com/HenryS1/herodotus.git :branch master

1.2Basic usage

This library provides a macro that generates JSON serialisation and deserialisation at the same time as defining a CLOS class.

For example the below definition creates a CLOS class with two fields x and y which have obvious accessors (x and y) and initargs (:x and :y).

CL-USER> (herodotus:define-json-model point (x y))

It also defines a package named point-json with a function for parsing json point-json:from-json and also implements a generic method for writing to json in the herodotus package.

CL-USER> (defvar *point* (point-json:from-json "{ \"x\": 1, \"y\": 2 }"))
*POINT*
CL-USER> (x *point*)
1
CL-USER> (y *point*)
2
CL-USER> (herodotus:to-json *point*)
"{\"x\":1,\"y\":2}"

1.3Nested classes

You can also define classes that have class members using the type specifier syntax. This block defines two json models tree and branch. A tree has branch members and the branch members will be parsed from json using the parser defined for the tree.

CL-USER> (herodotus:define-json-model branch (size))
CL-USER> (herodotus:define-json-model tree ((branches branch)))

The syntax (branches branch) declares that the field named branches must be parsed as the type branch. Json models for nested classes need to be defined before the models for the classes they are nested in or an error will be thrown. The error is thrown at macro expansion time.

CL-USER> (herodotus:define-json-model test-no-parser ((things not-parseable)))
CL-USER> (herodotus:define-json-model test-no-parser ((things not-parseable)))
class-name TEST-NO-PARSER slots ((THINGS NOT-PARSEABLE))
; Evaluation aborted on #<SIMPLE-ERROR "Could not find parser for
; class NOT-PARSEABLE. Please define a json model for it."
; {100599D903}>.

1.4None, one or many semantics

Fields in class definitions are parsed as either nil (if missing from the json), a single instance if the field is not an array and isn't empty or a vector if the json contains an array of elements.

CL-USER> (herodotus:define-json-model numbers (ns))
CL-USER> (ns (numbers-json:from-json "{ }"))
NIL
CL_USER> (ns (numbers-json:from-json "{ \"ns\": 1 }"))
1
CL-USER> (ns (numbers-json:from-json "{ \"ns\": [1, 2, 3] }"))
#(1 2 3)

1.5Setting the parsing case

The macro define-json-model has an optional third argument which specifies the case convention for parsing json fields. The options for this argument are

:camel-case ;; camelCase
:kebab-case ;; kebab-case
:snake-case ;; snake_case
:screaming-snake-case ;; SCREAMING_SNAKE_CASE

The default value of the argument is :camel-case. Below is an example of changing the default case to snake case.

CL-USER> (herodotus:define-json-model snake-case (pet-snake) :snake-case)
CL-USER> (pet-snake (snake-case-json:from-json "{ \"pet_snake\": \"boa\" }"))
"boa"
CL-USER> (herodotus:to-json (snake-case-json:from-json "{ \"pet_snake\": \"boa\" }"))
"{\"pet_snake\":\"boa\"}"

1.6Special case field names

Parsing specific field names can be done using the third argument of a field specifier. If a special field name is provided it doesn't have to match the name of the slot in the CLOS class and can use any formatting convention.

CL-USER> (herodotus:define-json-model special-case ((unusual-format () "A_very-UniqueNAME"))
CL-USER> (unusual-format (special-case-json:from-json "{ \"A_very-UniqueNAME\": \"Phineas Fog\" }"))
"Phineas Fog"
CL-USER> (herodotus:to-json (special-case-json:from-json "{ \"A_very-UniqueNAME\": \"Phineas Fog\" }"))
"{\"A_very-UniqueNAME\":\"Phineas Fog\"}"

1.7Macro specification

The define-json-model macro takes three arguments: name, slots and an optional argument for case-type. The name argument is the name of the generated CLOS class. The slots argument is a collection of slot descriptors and the case-type argument is a keyword.

Slot descriptors can be either symbols or lists. If a slot descriptor is a symbol then the value of the corresponding CLOS slot will be a deserialised json primitive in lisp form: a number, boolean, string, vector (for arrays), or hash-table (for objects).

If a slot descriptor is a list then first argument is the CLOS slot name, the second argument is either () or the name of a previously defined json model to deserialise the value of this field to. The optional third argument is a special case name for this field which can have custom formatting.

The case-type keyword argument is one of :screaming-snake-case, :snake-case, :kebab-case and :camel-case and defines the formatting convention for field names in a json object.

1.8Dependencies

The project depends on YASON which does the json parsing and serialisation under the hood, CL-PPCRE for text manipulation during code generation and Alexandria for macro writing utilities.

1.9License

This project is provided under the MIT license. See the LICENSE file for details.

Dependencies (4)

  • alexandria
  • cl-ppcre
  • rove
  • yason

Dependents (0)

    • GitHub
    • Quicklisp