srfi-180/README.org

217 lines
10 KiB
Org Mode
Raw Permalink Normal View History

2024-09-13 21:59:25 +00:00
# Created 2024-09-14 Sat 00:15
#+title: SRFI-180
#+author: Daniel Ziltener
#+export_file_name: README.org
#+property: header-args:scheme :session *chicken* :comments none
#+property: header-args:fundamental :eval no
* Dependencies
Main dependencies:
#+name: dependencies
| Egg | Description |
|----------+--------------------|
| srfi-34 | Exception Handling |
| srfi-35 | Exception Types |
| srfi-158 | Generators |
|----------+--------------------|
Test dependencies:
#+name: test-dependencies
| Egg | Description |
|------+--------------------------------|
| test | The de-facto standard test egg |
* API
** Exceptions
This library defines an SRFI-35 exception type ~&json-error~ that gets raised when invalid tokens are encountered. The exception type has a field ~json-invalid-token~ that contains the offending token.
#+begin_src scheme
(define-condition-type &json-error &error
json-error?
(json-error-reason json-error-reason)
(json-invalid-token json-invalid-token))
#+end_src
** Parameters
This library offers the following configuration parameters:
#+name: parameters
| Parameter | Default | Description |
|--------------------------------+---------+-----------------------------------------------------|
| json-nesting-depth-limit | +inf.0 | the maximum nesting depth of JSON that can be read. |
| json-number-of-character-limit | +inf.0 | the maximum length of JSON input that can be read. |
** Predicates
For some reason, this SRFI includes a predicate to check for JSON null values:
#+begin_src scheme
(define (json-null? obj) (eq? obj 'null))
#+end_src
** Reading JSON
*** json-generator
~(json-generator [port-or-generator]) → generator~
Streaming event-based JSON reader. =PORT-OR-GENERATOR= default value is the value returned by =current-input-port=. It must be a textual input port or a generator of characters. =json-generator= returns a generator of Scheme objects, each of which must be one of:
- ~'array-start~ symbol denoting that an array should be constructed.
- ~'array-end~ symbol denoting that the construction of the array for which the last ~'array-start~ was generated and not closed is finished.
- ~'object-start~ symbol denoting that an object should be constructed. The object's key-value pairs are emitted in sequence like those in a property list (plist) where keys are strings. That is, the generation of a key is always followed by the generation of a value. Otherwise, the JSON would be invalid and =json-generator= would raise an error.
- ~'object-end~ symbol denoting that the construction of the object for which the last ~'object-start~ was generated and not closed is finished.
- the symbol ~'null~
- boolean
- number
- string
In the case where nesting of arrays or objects reaches the value returned by the parameter =json-nesting-depth-limit=, the generator must raise an object that satisfies the predicate =json-error?=.
In cases where the JSON is invalid, the generator returned by =json-generator= should raise an object that satisfies the predicate =json-error?=.
Otherwise, if =PORT-OR-GENERATOR= contains valid JSON text, the generator returned by =json-generator= must yield an end-of-file object in two situations:
- The first time the generator returned by =json-generator= is called, it returns an object that is a boolean, a number, a string or the symbol ='null=.
- The first time the generator returned by =json-generator= is called, it returns a symbol that is not the symbol ='null=. When the underlying JSON text is valid, it should be the symbol starting a structure: ='object-start= or ='array-start=. The end-of-file object is generated when that structure is finished.
In other words, the generator returned by =json-generator= will parse at most one JSON value or one top-level structure. If =PORT= is not finished, as in the case of JSON lines, the user should call =json-generator= again with the same =PORT-OR-GENERATOR=.
**** Examples
#+begin_src scheme
(call-with-input-string "42 101 1337" (lambda (port) (generator->list (json-generator port))))
#+end_src
#+results:
#+begin_src scheme
(42)
#+end_src
#+begin_src scheme
(call-with-input-string "[42] 101 1337" (lambda (port) (generator->list (json-generator port))))
#+end_src
#+results:
#+begin_src scheme
(array-start 42 array-end)
#+end_src
*** json-fold
~(json-fold proc array-start array-end object-start object-end seed [port-or-generator])~
Fundamental JSON iterator.
=json-fold= will read the JSON text from =PORT-OR-GENERATOR=, which has ~(current-input-port)~ as its default value. =json-fold= will call the procedures passed as argument:
- ~(PROC obj seed)~ is called when a JSON value is generated or a complete JSON structure is read. =PROC= should return the new seed that will be used to iterate over the rest of the generator. Termination is described below.
- ~(OBJECT-START seed)~ is called with a seed and should return a seed that will be used as the seed of the iteration over the key and values of that object.
- ~(OBJECT-END seed)~ is called with a seed and should return a new seed that is the result of the iteration over a JSON object.
=ARRAY-START= and =ARRAY-END= take the same arguments, and have similar behavior, but are called for iterating on JSON arrays.
=json-fold= must return the seed when:
- =PORT-OR-GENERATOR= yields an object that satisfies the predicate =eof-object?=
- All structures, array or object, that were started have ended. The returned object is ~(PROC obj SEED)~ where obj is the object returned by =ARRAY-END= or =OBJECT-END=
*** json-read
~(json-read [port-or-generator]) → object~
JSON reader procedure. =PORT-OR-GENERATOR= must be a textual input port or a generator of characters. The default value of =PORT-OR-GENERATOR= is the value returned by the procedure =current-input-port=. The returned value is a Scheme object. =json-read= must return only the first toplevel JSON value or structure. When there are multiple toplevel values or structures in =PORT-OR-GENERATOR=, the user should call =json-read= several times to read all of it.
The mapping between JSON types and Scheme objects is the following:
- =null= → the symbol ='null=
- =true==#t=
- =false==#f=
- =number= → number
- =string= → string
- =array= → vector
- =object= → association list with keys that are symbols
In the case where nesting of arrays or objects reaches the value returned by the parameter =json-nesting-depth-limit=, =json-read= must raise an object that satisfies the predicate =json-error?=
*** json-lines-read
~(json-lines-read [port-or-generator]) → generator~
JSON reader of jsonlines or ndjson. As its first and only argument, it takes a generator of characters or a textual input port whose default value is the value returned by =current-input-port=. It will return a generator of Scheme objects as specified in =json-read=.
*** json-sequence-read
~(json-sequence-read [port-or-generator]) → generator~
JSON reader of JSON Text Sequences (RFC 7464). As its first and only argument, it takes a generator of characters or a textual input port whose default value is the value returned by =current-input-port=. It will return a generator of Scheme objects as specified in =json-read=.
*** json-accumulator
~(json-accumulator port-or-accumulator) → procedure~
Streaming event-based JSON writer. =PORT-OR-ACCUMULATOR= must be a textual output port or an accumulator that accepts characters and strings. It returns an accumulator procedure that accepts Scheme objects as its first and only argument and that follows the same protocol as described in =json-generator=. Any deviation from the protocol must raise an error that satisfies =json-error?=. In particular, objects and arrays must be properly nested.
Mind the fact that most JSON parsers have a nesting limit that is not documented by the standard. Even if you can produce arbitrarily nested JSON with this library, you might not be able to read it with another library.
*** json-write
~(json-write obj [port-or-accumulator]) → unspecified~
JSON writer procedure. =PORT-OR-ACCUMULATOR= must be a textual output port, or an accumulator that accepts characters and strings. The default value of =PORT-OR-ACCUMULATOR= is the value returned by the procedure =current-output-port=. The value returned by =json-write= is unspecified.
=json-write= will validate that =OBJ= can be serialized into JSON before writing to =PORT=. An error that satisfies =json-error?= is raised in the case where =OBJ= is not an object or a composition of the following types:
- symbol ='null=
- boolean
- number. Must be integers or inexact rationals. (That is, they must not be complex, infinite, NaN, or exact rationals that are not integers.)
- string
- vector
- association list with keys as symbols
* About this egg
** Source
The source is available at [[https://gitea.lyrion.ch/Chicken/srfi-180]].
** Author
Daniel Ziltener
** Version History
#+name: version-history
| 1.5 | Reimplementation |
| 1.0 | Reference Implementation |
* License
#+begin_src fundamental
Copyright (C) 2022 Daniel Ziltener
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
,* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
,* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
,* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#+end_src