Jeison: An Emacs library for declarative JSON parsing
source link: https://www.tuicool.com/articles/UvmMnyF
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
jeison
Jeison is a library for transforming JSON objects (or alist s) into EIEIO objects.
Installation
TODO: add to MELPA
Main idea
Main idea behind jeison is to create an easier way of dealing with deep JSON objects that contain lots and lots of information. Converting those into your own internal structures (or simply working with them) might become a burden pretty fast. Jeison helps to avoid writing tons of boilerplate code and simply declare how to find your data inside of JSON objects.
In order to use jeison
one must first define a class using jeison-defclass
. It works absolutely like defclass
from EIEIO, but allows you to add another property to the class slots: :path
. Here is an example:
(jeison-defclass my-first-jeison-class nil ((name :initarg :name :path (personal name last)) (job :initarg :job :path (job title))))
Defining class like this we tell jeison
where it should fetch values for name
and job
slots. In JavaScript
syntax, it would look similar to the following code:
name = json['personal']['name']['last']; job = json['job']['title'];
The next step would be transforming actual JSON object into EIEIO object. This is done by function jeison-read
. Let's assume that we have the following JSON object in the Emacs Lisp variable json-person
:
// json-person { "personal": { "address": { }, "name": { "first": "John", "last": "Johnson" } }, "job": { "company": "Good Inc.", "title": "CEO" }, "skills": [ ] }
Calling jeison-read
will produce the following results:
(setq person (jeison-read my-first-jeison-class json-person)) (oref person name) ;; => "Johnson" (oref person job) ;; => "CEO"
jeison-read
also accepts optional third argument that contains a path to sub-object that we should read. For example, we can use jeison
just to get one value from JSON object:
(jeison-read 'string json-person '(personal name last)) ;; => "Johnson"
Features
Default paths
In many cases, classes that we use in code significantly resemble the structure of the source JSON object. This means that :path
will have same value as the corresponding slot's name. In order to avoid this duplication, jeison
allows to omit :path
property and use slot's name as a default:
(jeison-defclass default-path-class nil ((first) (last) (full)))
// json-name { "name": { "first": "John", "last": "Johnson", "full": "John Johnson" } }
(setq name (jeison-read default-path-class json-name '(name))) (oref name first) ;; => "John" (oref name last) ;; => "Johnson" (oref name full) ;; => "John Johnson"
Type checks
Jeison checks that type that user wants to read from JSON object matches the one that was actually found:
(jeison-read 'string '((x . 1)) 'x) ;; => (jeison-wrong-parsed-type string 1)
Nested objects
EIEIO allows annotating class slots with types . Besides checking the type of the found object, Jeison uses this information for constructing nested objects .
Let's consider the following example:
(jeison-defclass jeison-person nil ((name :initarg :name :path (personal name last)) (job :initarg :job :type jeison-job))) (jeison-defclass jeison-job nil ((company :initarg :company) (position :initarg :position :path title) (location :initarg :location :path (location city))))
In this example, jeison-person
has a slot that has a type of another jeison
class: jeison-job
. As the result of this hierarchy, for the next JSON object:
// json-person { "personal": { "address": { }, "name": { "first": "John", "last": "Johnson" } }, "job": { "company": "Good Inc.", "title": "CEO", "location": { "country": "Norway", "city": "Oslo" } }, "skills": [ ] }
We have these results:
(setq person (jeison-read jeison-person json-person)) (oref person name) ;; => "Johnson" (setq persons-job (oref person job)) (oref persons-job company) ;; => "Good Inc." (oref persons-job position) ;; => "CEO" (oref persons-job location) ;; => "Oslo"
Lists
Another JSON's basic data type is array . Jeison can deal with arrays in two ways:
- ignore : jeison can behave like it is a normal value and do nothing about it
(jeison-read t "{\"numbers\": [1, 2, 10, 40, 100]}" 'numbers) ;; => [1 2 10 40 100]
It is a vector by the given path and jeison can simply return it.
However, sometimes we might want to have a list or check that all elements have certain type.
- type-specific processing : jeison can process JSON arrays based on the expected type
(jeison-read '(list-of integer) "{\"numbers\": [1, 2, 10, 40, 100]}" 'numbers) ;; => (1 2 10 40 100)
EIEIO defines a very handy type list-of
that we can use for processing array elements and checking that they match the corresponding type.
This mechanism also allows jeison to parse lists of nested objects . Let's continue our "John Johnson" example and add skill processing:
(jeison-defclass jeison-person nil ((name :initarg :name :path (personal name last)) (job :initarg :job :type jeison-job) (skills :initarg :skills :type (list-of jeison-skill)))) (jeison-defclass jeison-job nil ((company :initarg :company) (position :initarg :position :path title) (location :initarg :location :path (location city)))) (jeison-defclass jeison-skill nil ((name :initarg :name :type string) (level :initarg :level :type integer)))
For the following JSON object:
// json-person { "personal": { "address": { }, "name": { "first": "John", "last": "Johnson" } }, "job": { "company": "Good Inc.", "title": "CEO", "location": { "country": "Norway", "city": "Oslo" } }, "skills": [ { "name": "Programming", "level": 9 }, { "name": "Design", "level": 4 }, { "name": "Communication", "level": 1 } ] }
jeison produces these results:
(setq person (jeison-read jeison-person json-person)) (oref person name) ;; => "Johnson" (mapcar (lambda (skill) (format "%s: %i" (oref skill name) (oref skill level))) (oref person skills)) ;; => ("Programming: 9" "Design: 4" "Communication: 1")
Indexed elements
Sometimes, though, it is not required to process the whole list. Sometimes we want just one element, especially in case of heterogeneous arrays. For this use case, jeison
supports indices in its :path
properties.
Here is an example:
// json-company { "company": { "name": "Good Inc.", "CEOs": [ { "name": { "first": "Gunnar", "last": "Olafson" }, "founder": true }, { "name": "TJ-B", "cool": true }, { "name": { "first": "John", "last": "Johnson" } } ] } }
(jeison-read 'boolean json-company '(company CEOs 0 founder)) ;; => t
Unlike arguments to elt
function, indices can be negative
. Negative indices have the same semantics as in Python
language (enumeration from the end):
(jeison-read 'string json-company '(company CEOs -1 name last)) ;; => "Johnson" (jeison-read 'boolean json-company '(company CEOs -2 cool)) ;; => t
Turning regular classes into jeison classes
Additionally, jeison
has a feature of transforming existing classes declared with defclass
macro into jeison
classes:
(defclass usual-class nil ((x) (y))) (jeisonify usual-class) (setq parsed (jeison-read usual-class "{\"x\": 10, \"y\": 20}")) (oref parsed x) ;; => 10 (oref parsed y) ;; => 20
NOTE 1even if defclass
included :path
properties, jeison
will still use default paths.
NOTE 2 jeison
hacks into the structure of EIEIO classes and their slots. If the modified class relies on the purity of slot properties or class options, don't use jeisonify
functionality and create a new class instead.
Development
Jeison uses cask . After the installation of cask , install all dependencies and run tests:
$ cask install $ cask exec ert-runner
Contribute
All contributions are most welcome!
It might include any help: bug reports, questions on how to use it, feature suggestions, and documentation updates.
Recommend
-
23
JSON (JavaScript Object Notation) is a lightweight format, which is widely used to send data over the internet. The JSON format includes dictionaries and arrays. In this tutorial some data is requested from a web server a...
-
12
Despite the goal of JSON being a subset of JavaScript β which it failed to achieve β parsing JSON is quite unlike parsing a programming language. For invalid inputs, the sp...
-
18
README.md jeison
-
24
JSON is probably the most used standard file format for storing and transmitting data on the internet in recent times. Though it was historically derived from
-
3
binread: Declarative Rust Binary Parsing (If you want to skip the explanation of "why" and skip to the "what", jump to ) Why Rust? Rust, for t...
-
10
Towards GPGPU JSON parsing May 10, 2018 Update 2020-09-06: a followup to this post is The stack monoid. Th...
-
8
Alexander ZeitlerParsing JSON parameters stored in AWS Parameter Store using jqPublished on Tuesday, January 7, 2020The other day I had the idea to store some JSON as a
-
5
Parsing JSON at the CLI: A Practical Introduction to `jq` (and more!)Published December 21, 2020jq is a command line tool for parsing and modifying JSON. It is useful for extracting relevant bits of information from tool...
-
6
Manually parsing JSON with MoshiPosted at β Dec 21, 2020What is Moshi?Moshi is a fast and powerful JSON parsing library for the JVM and Android, built by th...
-
1
declarative binary format parsing language Reading and writing binary formats is hard, especially if itβs an interchange format that should work across a multitude of platforms and langua...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK