SkillAgentSearch skills...

Herodotus

A Common Lisp json library for CLOS classes

Install / Use

/learn @HenryS1/Herodotus
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

[[https://github.com/HenryS1/herodotus/tree/master][https://github.com/HenryS1/herodotus/actions/workflows/ci.yaml/badge.svg]]

  • Herodotus

** Installation

The library is available on quicklisp since the ~2021-04-11~ dist so you can install with #+begin_src lisp (ql:quickload :herodotus) #+end_src

To install from source clone the repo into your lisp sources directory and load with asdf #+begin_src lisp (asdf:load-system :herodotus) #+end_src or use [[https://github.com/fukamachi/qlot][qlot]] #+begin_src txt git herodotus https://github.com/HenryS1/herodotus.git :branch master #+end_src

** Basic 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~). #+begin_src lisp CL-USER> (herodotus:define-json-model point (x y)) #+end_src

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.

#+begin_src lisp 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}" #+end_src

** Nested 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~.

#+begin_src lisp CL-USER> (herodotus:define-json-model branch (size)) CL-USER> (herodotus:define-json-model tree ((branches branch))) #+end_src

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.

#+begin_src lisp 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}>. #+end_src

** None, 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.

#+begin_src lisp 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) #+end_src

** Setting 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

#+begin_src lisp :camel-case ;; camelCase :kebab-case ;; kebab-case :snake-case ;; snake_case :screaming-snake-case ;; SCREAMING_SNAKE_CASE #+end_src

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

#+begin_src lisp 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"}" #+end_src

** Special 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.

#+begin_src lisp 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"}" #+end_src

** Macro 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.

** Dependencies

The project depends on [[https://github.com/phmarek/yason][YASON]] which does the json parsing and serialisation under the hood, [[https://github.com/edicl/cl-ppcre][CL-PPCRE]] for text manipulation during code generation and [[https://github.com/kmx-io/alexandria][Alexandria]] for macro writing utilities.

** License

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

View on GitHub
GitHub Stars5
CategoryDevelopment
Updated10mo ago
Forks1

Languages

Common Lisp

Security Score

77/100

Audited on May 12, 2025

No findings