749 lines
31 KiB
XML
749 lines
31 KiB
XML
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
||
|
|
<?xml-stylesheet type="text/xsl" href="clixdoc.xsl"?>
|
||
|
|
|
||
|
|
<clix:documentation xmlns:clix="http://bknr.net/clixdoc"
|
||
|
|
xmlns="http://www.w3.org/1999/xhtml">
|
||
|
|
|
||
|
|
<clix:title>YASON - A JSON encoder/decoder for Common Lisp</clix:title>
|
||
|
|
|
||
|
|
<clix:short-description>
|
||
|
|
YASON is a JSON encoding and decoding library for Common Lisp. It
|
||
|
|
provides for functions to read JSON strings into Lisp data
|
||
|
|
structures and for serializing Lisp data structures as JSON
|
||
|
|
strings.
|
||
|
|
</clix:short-description>
|
||
|
|
|
||
|
|
<clix:abstract>
|
||
|
|
YASON is a Common Lisp library for encoding and decoding data in
|
||
|
|
the <a href="http://json.org/">JSON</a> interchange format. JSON
|
||
|
|
is used as a lightweight alternative to XML. YASON has the sole
|
||
|
|
purpose of encoding and decoding data and does not impose any
|
||
|
|
object model on the Common Lisp application that uses it.
|
||
|
|
</clix:abstract>
|
||
|
|
|
||
|
|
<clix:contents/>
|
||
|
|
|
||
|
|
<clix:chapter name="intro" title="Introduction">
|
||
|
|
<p>
|
||
|
|
<a href="http://json.org/">JSON</a> is an established
|
||
|
|
alternative to XML as a data interchange format for web
|
||
|
|
applications. YASON implements reading and writing of JSON
|
||
|
|
formatted data in Common Lisp. It does not attempt to provide a
|
||
|
|
mapping between CLOS objects and YASON, but can be used to
|
||
|
|
implement such mappings.
|
||
|
|
</p>
|
||
|
|
<p>
|
||
|
|
<a href="http://common-lisp.net/project/cl-json/">CL-JSON</a> is
|
||
|
|
another Common Lisp package that can be used to work with JSON
|
||
|
|
encoded data. It takes a more integrated approach, providing
|
||
|
|
for library internal mappings between JSON objects and CLOS
|
||
|
|
objects. YASON was created as a lightweight, documented
|
||
|
|
alternative with a minimalistic approach and extensibilty.
|
||
|
|
</p>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="install" title="Download and Installation">
|
||
|
|
<p>
|
||
|
|
YASON has its permanent home at <a
|
||
|
|
href="https://github.com/hanshuebner/yason/">GitHub</a>.
|
||
|
|
It can be obtained by downloading the <a
|
||
|
|
href="https://github.com/downloads/hanshuebner/yason/yason.tar.gz">release
|
||
|
|
tarball</a>. The current release is <clix:current-release/>.
|
||
|
|
</p>
|
||
|
|
<p>
|
||
|
|
You may also check out the current development version from its
|
||
|
|
<a href="http://github.com/hanshuebner/yason/">git
|
||
|
|
repository</a>. If you have suggestions regarding YASON, please
|
||
|
|
email me at <i>hans.huebner@gmail.com</i>.
|
||
|
|
</p>
|
||
|
|
<p>
|
||
|
|
YASON is written in ANSI Common Lisp. It depends on UNIT-TEST,
|
||
|
|
TRIVIAL-GRAY-STREAMS and ALEXANDRIA open source libraries. The
|
||
|
|
recommended way to install YASON and its dependencies is through
|
||
|
|
the excellent <a href="http://www.quicklisp.org/">Quicklisp</a>
|
||
|
|
library management system.
|
||
|
|
</p>
|
||
|
|
<p>
|
||
|
|
YASON lives in the <b>:yason</b> package and creates a package
|
||
|
|
nickname <b>:json</b>. Applications will not normally
|
||
|
|
<b>:use</b> this package, but rather use qualified names to
|
||
|
|
access YASON's symbols. For that reason, YASON's symbols do not
|
||
|
|
contain the string "JSON" themselves. See below for usage
|
||
|
|
samples.
|
||
|
|
</p>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="json-package" title="Using JSON as package name">
|
||
|
|
Versions of YASON preceding the v0.6.0 release provided a package
|
||
|
|
nickname "JSON" for the "YASON" package. This made it impossible
|
||
|
|
to load both YASON and CL-JSON into the same image, because
|
||
|
|
CL-JSON uses the "JSON" package name as well.
|
||
|
|
|
||
|
|
<p>
|
||
|
|
As CL-JSON's use of "JSON" as package name has a much longer
|
||
|
|
history and loading of both CL-JSON and YASON into the same
|
||
|
|
image has become more common, the "JSON" nickname was removed
|
||
|
|
from the YASON package with the v0.6.0 release. Users will need
|
||
|
|
to change their applications so that the "JSON" nickname is no
|
||
|
|
longer used to refer to the "YASON" package. It is understood
|
||
|
|
that this is a disruptive change, but as there is no
|
||
|
|
all-encompassing workaround, this step was felt to be the right
|
||
|
|
one to make
|
||
|
|
</p>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="mapping" title="Mapping between JSON and CL datatypes">
|
||
|
|
By default, YASON performs the following mappings between JSON and
|
||
|
|
CL datatypes:
|
||
|
|
<table border="1">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>JSON<br/>datatype</th>
|
||
|
|
<th>CL<br/>datatype</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>object</td>
|
||
|
|
<td>hash-table<br/>:test #'equal</td>
|
||
|
|
<td>
|
||
|
|
Keys are strings by default (see
|
||
|
|
<clix:ref>*parse-object-key-fn*</clix:ref>). Set
|
||
|
|
<clix:ref>*parse-object-as*</clix:ref> to <b>:alist</b> in
|
||
|
|
order to have YASON parse objects as alists or to
|
||
|
|
<b>:plist</b> to parse them as plists. When using plists,
|
||
|
|
you probably want to also set
|
||
|
|
<clix:ref>*parse-object-key-fn*</clix:ref> to a function
|
||
|
|
that interns the object's keys to symbols.
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>array</td>
|
||
|
|
<td>list</td>
|
||
|
|
<td>
|
||
|
|
Can be changed to read to vectors (see
|
||
|
|
<clix:ref>*parse-json-arrays-as-vectors*</clix:ref>).
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>string</td>
|
||
|
|
<td>string</td>
|
||
|
|
<td>
|
||
|
|
JSON escape characters are recognized upon reading. Upon
|
||
|
|
writing, known escape characters are used, but non-ASCII
|
||
|
|
Unicode characters are written as is.
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>number</td>
|
||
|
|
<td>number</td>
|
||
|
|
<td>
|
||
|
|
Parsed with READ, printed with PRINC. This is not a
|
||
|
|
faithful implementation of the specification.
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>true</td>
|
||
|
|
<td>t</td>
|
||
|
|
<td>
|
||
|
|
Can be changed to read as TRUE (see
|
||
|
|
<clix:ref>*parse-json-booleans-as-symbols*</clix:ref>).
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>false</td>
|
||
|
|
<td>nil</td>
|
||
|
|
<td>
|
||
|
|
Can be changed to read as FALSE (see
|
||
|
|
<clix:ref>*parse-json-booleans-as-symbols*</clix:ref>).
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>null</td>
|
||
|
|
<td>nil</td>
|
||
|
|
<td></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="parsing" title="Parsing JSON data">
|
||
|
|
<p>
|
||
|
|
JSON data is always completely parsed into an equivalent
|
||
|
|
in-memory representation. Upon reading, some translations are
|
||
|
|
performed by default to make it easier for the Common Lisp
|
||
|
|
program to work with the data; see <clix:ref>mapping</clix:ref>
|
||
|
|
for details. If desired, the parser can be configured to
|
||
|
|
preserve the full semantics of the JSON data read.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
For example
|
||
|
|
|
||
|
|
<pre>CL-USER> (defvar *json-string* "[{\"foo\":1,\"bar\":[7,8,9]},2,3,4,[5,6,7],true,null]")
|
||
|
|
*JSON-STRING*
|
||
|
|
CL-USER> (let* ((result (yason:parse *json-string*)))
|
||
|
|
(print result)
|
||
|
|
(alexandria:hash-table-plist (first result)))
|
||
|
|
|
||
|
|
(#<HASH-TABLE :TEST EQUAL :COUNT 2 {5A4420F1}> 2 3 4 (5 6 7) T NIL)
|
||
|
|
("bar" (7 8 9) "foo" 1)
|
||
|
|
CL-USER> (defun maybe-convert-to-keyword (js-name)
|
||
|
|
(or (find-symbol (string-upcase js-name) :keyword)
|
||
|
|
js-name))
|
||
|
|
MAYBE-CONVERT-TO-KEYWORD
|
||
|
|
CL-USER> :FOO ; intern the :FOO keyword
|
||
|
|
:FOO
|
||
|
|
CL-USER> (let* ((yason:*parse-json-arrays-as-vectors* t)
|
||
|
|
(yason:*parse-json-booleans-as-symbols* t)
|
||
|
|
(yason:*parse-object-key-fn* #'maybe-convert-to-keyword)
|
||
|
|
(result (yason:parse *json-string*)))
|
||
|
|
(print result)
|
||
|
|
(alexandria:hash-table-plist (aref result 0)))
|
||
|
|
|
||
|
|
#(#<HASH-TABLE :TEST EQUAL :COUNT 2 {59B4EAD1}> 2 3 4 #(5 6 7) YASON:TRUE NIL)
|
||
|
|
("bar" #(7 8 9) :FOO 1)</pre>
|
||
|
|
|
||
|
|
<p>
|
||
|
|
The second example modifies the parser's behaviour so that JSON
|
||
|
|
arrays are read as CL vectors, JSON booleans will be read as the
|
||
|
|
symbols TRUE and FALSE and JSON object keys will be looked up in
|
||
|
|
the <b>:keyword</b> package. Interning strings coming from an
|
||
|
|
external source is not recommended practice.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<clix:subchapter name="parser-dict" title="Parser dictionary">
|
||
|
|
<clix:function name="parse">
|
||
|
|
<clix:lambda-list>input &key (object-key-fn
|
||
|
|
*parse-object-as-key-fn*) (object-as *parse-object-as*)
|
||
|
|
(json-arrays-as-vectors *parse-json-arrays-as-vectors*)
|
||
|
|
(json-booleans-as-symbols *parse-json-booleans-as-symbols*)
|
||
|
|
(json-nulls-as-keyword *parse-json-null-as-keyword*)</clix:lambda-list>
|
||
|
|
<clix:returns>object</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Parse <clix:arg>input</clix:arg>, which must be a string or
|
||
|
|
a stream, as JSON. Returns the Lisp representation of the
|
||
|
|
JSON structure parsed.
|
||
|
|
<p>
|
||
|
|
The keyword arguments <clix:arg>object-key-fn</clix:arg>,
|
||
|
|
<clix:arg>object-as</clix:arg>,
|
||
|
|
<clix:arg>json-arrays-as-vectors</clix:arg>,
|
||
|
|
<clix:arg>json-booleans-as-symbols</clix:arg>, and
|
||
|
|
<clix:arg>json-null-as-keyword</clix:arg> may be used
|
||
|
|
to specify different values for the parsing parameters
|
||
|
|
from the current bindings of the respective special
|
||
|
|
variables.
|
||
|
|
</p>
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:special-variable name="*parse-json-arrays-as-vectors*">
|
||
|
|
<clix:description>
|
||
|
|
If set to a true value, JSON arrays will be parsed as
|
||
|
|
vectors, not as lists. NIL is the default.
|
||
|
|
</clix:description>
|
||
|
|
</clix:special-variable>
|
||
|
|
|
||
|
|
<clix:special-variable name="*parse-object-as*">
|
||
|
|
<clix:description>
|
||
|
|
Can be set to <b>:hash-table</b> to parse objects as hash
|
||
|
|
tables, <b>:alist</b> to parse them as alists or
|
||
|
|
<b>:plist</b> to parse them as plists. <b>:hash-table</b>
|
||
|
|
is the default.
|
||
|
|
</clix:description>
|
||
|
|
</clix:special-variable>
|
||
|
|
|
||
|
|
<clix:special-variable name="*parse-json-booleans-as-symbols*">
|
||
|
|
<clix:description>
|
||
|
|
If set to a true value, JSON booleans will be read as the
|
||
|
|
symbols TRUE and FALSE instead of T and NIL, respectively.
|
||
|
|
NIL is the default.
|
||
|
|
</clix:description>
|
||
|
|
</clix:special-variable>
|
||
|
|
|
||
|
|
<clix:special-variable name="*parse-json-null-as-keyword*">
|
||
|
|
<clix:description>
|
||
|
|
If set to a true value, JSON null will be read as the
|
||
|
|
keyword :NULL, instead of NIL.
|
||
|
|
NIL is the default.
|
||
|
|
</clix:description>
|
||
|
|
</clix:special-variable>
|
||
|
|
|
||
|
|
<clix:special-variable name="*parse-object-key-fn*">
|
||
|
|
<clix:description>
|
||
|
|
Function to call to convert a key string in a JSON object to
|
||
|
|
a key in the CL hash produced. IDENTITY is the default.
|
||
|
|
</clix:description>
|
||
|
|
</clix:special-variable>
|
||
|
|
|
||
|
|
</clix:subchapter>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="encoding" title="Encoding JSON data">
|
||
|
|
YASON provides two distinct modes to encode JSON data:
|
||
|
|
applications can either create an in-memory representation of the
|
||
|
|
data to be serialized, then have YASON convert it to JSON in one
|
||
|
|
go, or they can use a set of macros to serialze the JSON data
|
||
|
|
element-by-element, allowing fine-grained control over the layout
|
||
|
|
of the generated data.
|
||
|
|
|
||
|
|
<p>
|
||
|
|
Optionally, the JSON that is produced can be indented.
|
||
|
|
Indentation requires the use of a
|
||
|
|
<clix:ref>JSON-OUTPUT-STREAM</clix:ref> as serialization target.
|
||
|
|
With the stream serializer, such a stream is automatically used.
|
||
|
|
If indentation is desired with the DOM serializer, such a stream
|
||
|
|
can be obtained by calling the
|
||
|
|
<clix:ref>MAKE-JSON-OUTPUT-STREAM</clix:ref> function with the
|
||
|
|
target output string as argument. Please be aware that indented
|
||
|
|
output not requires more space, but is also slower and should
|
||
|
|
not be enabled in performance critical applications.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<clix:subchapter name="dom-encoder" title="Encoding a JSON DOM">
|
||
|
|
<p>
|
||
|
|
In this mode, an in-memory structure is encoded in JSON
|
||
|
|
format. The structure must consist of objects that are
|
||
|
|
serializable using the <clix:ref>ENCODE</clix:ref> function.
|
||
|
|
YASON defines a number of encoders for standard data types
|
||
|
|
(see <clix:ref>MAPPING</clix:ref>), but the application can
|
||
|
|
define additional methods (e.g. for encoding CLOS objects).
|
||
|
|
</p>
|
||
|
|
For example:
|
||
|
|
<pre>CL-USER> (yason:encode
|
||
|
|
(list (alexandria:plist-hash-table
|
||
|
|
'("foo" 1 "bar" (7 8 9))
|
||
|
|
:test #'equal)
|
||
|
|
2 3 4
|
||
|
|
'(5 6 7)
|
||
|
|
t nil)
|
||
|
|
*standard-output*)
|
||
|
|
[{"foo":1,"bar":[7,8,9]},2,3,4,[5,6,7],true,null]
|
||
|
|
(#<HASH-TABLE :TEST EQUAL :COUNT 2 {59942D21}> 2 3 4 (5 6 7) T NIL)</pre>
|
||
|
|
|
||
|
|
<clix:subchapter name="dom-encoder-dict" title="DOM encoder dictionary">
|
||
|
|
<clix:function name="encode" generic="true">
|
||
|
|
<clix:lambda-list>object &optional
|
||
|
|
stream</clix:lambda-list>
|
||
|
|
<clix:returns>object</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encode <clix:arg>object</clix:arg> in JSON format and
|
||
|
|
write to <clix:arg>stream</clix:arg>. May be specialized
|
||
|
|
by applications to perform specific rendering. Stream
|
||
|
|
defaults to *STANDARD-OUTPUT*.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-alist">
|
||
|
|
<clix:lambda-list>object &optional (stream
|
||
|
|
*standard-output*)</clix:lambda-list>
|
||
|
|
<clix:returns>object</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encodes <clix:arg>object</clix:arg>, an alist, in JSON
|
||
|
|
format and write to <clix:arg>stream</clix:arg>.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-plist">
|
||
|
|
<clix:lambda-list>object &optional (stream
|
||
|
|
*standard-output*)</clix:lambda-list>
|
||
|
|
<clix:returns>object</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encodes <clix:arg>object</clix:arg>, a plist, in JSON
|
||
|
|
format and write to <clix:arg>stream</clix:arg>.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="make-json-output-stream">
|
||
|
|
<clix:lambda-list>stream &key (indent t)</clix:lambda-list>
|
||
|
|
<clix:returns>stream</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Creates a <clix:ref>json-output-stream</clix:ref> instance
|
||
|
|
that wraps the supplied <clix:arg>stream</clix:arg> and
|
||
|
|
optionally performs indentation of the generated JSON
|
||
|
|
data. The <clix:arg>indent</clix:arg> argument is
|
||
|
|
described in <clix:ref>WITH-OUTPUT</clix:ref>. Note that
|
||
|
|
if the <clix:arg>indent</clix:arg> argument is NIL, the
|
||
|
|
original stream is returned in order to avoid the
|
||
|
|
performance penalty of the indentation algorithm.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:special-variable name="*list-encoder*">
|
||
|
|
<clix:description>
|
||
|
|
Function to call to translate a CL list into JSON data.
|
||
|
|
<clix:arg>'YASON:ENCODE-PLAIN-LIST-TO-ARRAY</clix:arg> is the default;
|
||
|
|
<clix:arg>'YASON:ENCODE-PLIST</clix:arg> and
|
||
|
|
<clix:arg>'YASON:ENCODE-ALIST</clix:arg> are available to produce
|
||
|
|
JSON objects.
|
||
|
|
<p>
|
||
|
|
This is useful to translate a deeply recursive structure in a single
|
||
|
|
<clix:arg>YASON:ENCODE</clix:arg> call.
|
||
|
|
</p>
|
||
|
|
</clix:description>
|
||
|
|
</clix:special-variable>
|
||
|
|
|
||
|
|
|
||
|
|
<clix:special-variable name="*symbol-key-encoder*">
|
||
|
|
<clix:description>
|
||
|
|
Defines the policy to encode symbols as keys (eg. in hash tables).
|
||
|
|
The default is to error out, to provide backwards-compatible behaviour.
|
||
|
|
<p>
|
||
|
|
A useful function that can be bound to this variable is
|
||
|
|
<clix:ref>YASON:ENCODE-SYMBOL-AS-LOWERCASE.</clix:ref>
|
||
|
|
</p>
|
||
|
|
</clix:description>
|
||
|
|
</clix:special-variable>
|
||
|
|
</clix:subchapter>
|
||
|
|
</clix:subchapter>
|
||
|
|
|
||
|
|
<clix:subchapter name="stream-encoder" title="Encoding JSON in streaming mode">
|
||
|
|
<p>
|
||
|
|
In this mode, the JSON structure is generated in a stream.
|
||
|
|
The application makes explicit calls to the encoding library
|
||
|
|
in order to generate the JSON structure. It provides for more
|
||
|
|
control over the generated output, and can be used to generate
|
||
|
|
arbitary JSON without requiring that there exists a directly
|
||
|
|
matching Lisp data structure. The streaming API uses the
|
||
|
|
<clix:ref>encode</clix:ref> function, so it is possible to
|
||
|
|
intermix the two (see <clix:ref>app-encoders</clix:ref> for an
|
||
|
|
example).
|
||
|
|
</p>
|
||
|
|
For example:
|
||
|
|
<pre>CL-USER> (yason:with-output (*standard-output*)
|
||
|
|
(yason:with-array ()
|
||
|
|
(dotimes (i 3)
|
||
|
|
(yason:encode-array-element i))))
|
||
|
|
[0,1,2]
|
||
|
|
NIL
|
||
|
|
CL-USER> (yason:with-output (*standard-output*)
|
||
|
|
(yason:with-object ()
|
||
|
|
(yason:encode-object-element "hello" "hu hu")
|
||
|
|
(yason:with-object-element ("harr")
|
||
|
|
(yason:with-array ()
|
||
|
|
(dotimes (i 3)
|
||
|
|
(yason:encode-array-element i))))))
|
||
|
|
{"hello":"hu hu","harr":[0,1,2]}
|
||
|
|
NIL</pre>
|
||
|
|
|
||
|
|
<clix:subchapter name="stream-encoder-dict" title="Streaming encoder dictionary">
|
||
|
|
<clix:function name="with-output" macro="true">
|
||
|
|
<clix:lambda-list>(stream &key indent) &body body</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Set up a JSON streaming encoder context on
|
||
|
|
<clix:arg>stream</clix:arg>, then evaluate
|
||
|
|
<clix:arg>body</clix:arg>. <clix:arg>indent</clix:arg>
|
||
|
|
can be set to T to enable indentation with a default
|
||
|
|
indentation width or to an integer specifying the desired
|
||
|
|
indentation width. By default, indentation is switched
|
||
|
|
off.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="with-output-to-string*" macro="true">
|
||
|
|
<clix:lambda-list>(&key indent stream-symbol) &body body</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Set up a JSON streaming encoder context on
|
||
|
|
<clix:arg>stream-symbol</clix:arg> (by default
|
||
|
|
a gensym), then evaluate
|
||
|
|
<clix:arg>body</clix:arg>. Return a string with the
|
||
|
|
generated JSON output. See
|
||
|
|
<clix:ref>WITH-OUTPUT</clix:ref> for the description of
|
||
|
|
the <clix:arg>indent</clix:arg> keyword argument.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:condition name="no-json-output-context">
|
||
|
|
<clix:description>
|
||
|
|
This condition is signalled when one of the stream
|
||
|
|
encoding functions is used outside the dynamic context of
|
||
|
|
a <clix:ref>WITH-OUTPUT</clix:ref> or
|
||
|
|
<clix:ref>WITH-OUTPUT-TO-STRING*</clix:ref> body.
|
||
|
|
</clix:description>
|
||
|
|
</clix:condition>
|
||
|
|
|
||
|
|
<clix:function name="with-array" macro="true">
|
||
|
|
<clix:lambda-list>() &body body</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Open a JSON array, then run <clix:arg>body</clix:arg>.
|
||
|
|
Inside the body, <clix:ref>ENCODE-ARRAY-ELEMENT</clix:ref>
|
||
|
|
must be called to encode elements to the opened array.
|
||
|
|
Must be called within an existing JSON encoder context
|
||
|
|
(see <clix:ref>WITH-OUTPUT</clix:ref> and
|
||
|
|
<clix:ref>WITH-OUTPUT-TO-STRING*</clix:ref>).
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-array-element">
|
||
|
|
<clix:lambda-list>object</clix:lambda-list>
|
||
|
|
<clix:returns>object</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encode <clix:arg>object</clix:arg> as next array element to
|
||
|
|
the last JSON array opened
|
||
|
|
with <clix:ref>WITH-ARRAY</clix:ref> in the dynamic
|
||
|
|
context. <clix:arg>object</clix:arg> is encoded using the
|
||
|
|
<clix:ref>ENCODE</clix:ref> generic function, so it must be of
|
||
|
|
a type for which an <clix:ref>ENCODE</clix:ref> method is
|
||
|
|
defined.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-array-elements">
|
||
|
|
<clix:lambda-list>&rest objects</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encode <clix:arg>objects</clix:arg>, a series of JSON
|
||
|
|
encodable objects, as the next array elements in a JSON
|
||
|
|
array opened with
|
||
|
|
<clix:ref>WITH-ARRAY</clix:ref>. ENCODE-ARRAY-ELEMENTS
|
||
|
|
uses <clix:ref>ENCODE-ARRAY-ELEMENT</clix:ref>, which must
|
||
|
|
be applicable to each object in the list
|
||
|
|
(i.e. <clix:ref>ENCODE</clix:ref> must be defined for each
|
||
|
|
object type). Additionally, this must be called within a
|
||
|
|
valid stream context.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="with-object" macro="true">
|
||
|
|
<clix:lambda-list>() &body body</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Open a JSON object, then run <clix:arg>body</clix:arg>.
|
||
|
|
Inside the body,
|
||
|
|
<clix:ref>ENCODE-OBJECT-ELEMENT</clix:ref> or
|
||
|
|
<clix:ref>WITH-OBJECT-ELEMENT</clix:ref> must be called to
|
||
|
|
encode elements to the object. Must be called within an
|
||
|
|
existing JSON encoder
|
||
|
|
<clix:ref>WITH-OUTPUT</clix:ref> and
|
||
|
|
<clix:ref>WITH-OUTPUT-TO-STRING*</clix:ref>.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="with-object-element" macro="true">
|
||
|
|
<clix:lambda-list>(key) &body body</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Open a new encoding context to encode a JSON object
|
||
|
|
element. <clix:arg>key</clix:arg> is the key of the
|
||
|
|
element. The value will be whatever
|
||
|
|
<clix:arg>body</clix:arg> serializes to the current JSON
|
||
|
|
output context using one of the stream encoding functions.
|
||
|
|
This can be used to stream out nested object structures.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-object-element">
|
||
|
|
<clix:lambda-list>key value</clix:lambda-list>
|
||
|
|
<clix:returns>value</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encode <clix:arg>key</clix:arg> and
|
||
|
|
<clix:arg>value</clix:arg> as object element to the last
|
||
|
|
JSON object opened with <clix:ref>WITH-OBJECT</clix:ref>
|
||
|
|
in the dynamic context. <clix:arg>key</clix:arg> and
|
||
|
|
<clix:arg>value</clix:arg> are encoded using the
|
||
|
|
<clix:ref>ENCODE</clix:ref> generic function, so they both
|
||
|
|
must be of a type for which an <clix:ref>ENCODE</clix:ref>
|
||
|
|
method is defined.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-object-elements">
|
||
|
|
<clix:lambda-list>&rest elements</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encodes the parameters into JSON in the last object opened
|
||
|
|
with <clix:ref>WITH-OBJECT</clix:ref> using
|
||
|
|
<clix:ref>ENCODE-OBJECT-ELEMENT</clix:ref>. The parameters
|
||
|
|
should consist of alternating key/value pairs, and this
|
||
|
|
must be called within a valid stream context.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-object-slots">
|
||
|
|
<clix:lambda-list>object slots</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Encodes each slot in SLOTS for OBJECT in the last object
|
||
|
|
opened with <clix:ref>WITH-OBJECT</clix:ref> using
|
||
|
|
<clix:ref>ENCODE-OBJECT-ELEMENT</clix:ref>. The key is the
|
||
|
|
slot name, and the value is the slot value for the slot on
|
||
|
|
OBJECT. It is equivalent to
|
||
|
|
<pre>(loop for slot in slots
|
||
|
|
do (encode-object-element (string slot)
|
||
|
|
(slot-value object slot)))
|
||
|
|
</pre>
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-slots">
|
||
|
|
<clix:lambda-list>object</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Generic function to encode object slots. There is no default
|
||
|
|
implementation.
|
||
|
|
It should be called in an object encoding context. It uses
|
||
|
|
PROGN combinatation with MOST-SPECIFIC-LAST order, so that
|
||
|
|
base class slots are encoded before derived class slots.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:function name="encode-object">
|
||
|
|
<clix:lambda-list>object</clix:lambda-list>
|
||
|
|
<clix:returns>result*</clix:returns>
|
||
|
|
<clix:description>
|
||
|
|
Generic function to encode an object. The default implementation
|
||
|
|
opens a new object encoding context and calls
|
||
|
|
<clix:ref>ENCODE-SLOTS</clix:ref> on the argument.
|
||
|
|
</clix:description>
|
||
|
|
</clix:function>
|
||
|
|
|
||
|
|
<clix:class name="json-output-stream">
|
||
|
|
<clix:description>
|
||
|
|
Instances of this class are used to wrap an output stream
|
||
|
|
that is used as a serialization target in the stream
|
||
|
|
encoder and optionally in the DOM encoder if indentation
|
||
|
|
is desired. The class name is not exported, use
|
||
|
|
<clix:ref>make-json-output-stream</clix:ref> to create a
|
||
|
|
wrapper stream if required.
|
||
|
|
</clix:description>
|
||
|
|
</clix:class>
|
||
|
|
</clix:subchapter>
|
||
|
|
</clix:subchapter>
|
||
|
|
|
||
|
|
<clix:subchapter name="app-encoders" title="Application specific encoders">
|
||
|
|
|
||
|
|
Suppose your application uses structs to represent its data and
|
||
|
|
you want to encode these structs using JSON in order to send
|
||
|
|
them to a client application. Suppose further that your structs
|
||
|
|
also include internal information that you do not want to send.
|
||
|
|
Here is some code that illustrates how one could implement a
|
||
|
|
serialization function:
|
||
|
|
|
||
|
|
<pre>CL-USER> (defstruct user name age password)
|
||
|
|
USER
|
||
|
|
CL-USER> (defmethod yason:encode ((user user) &optional (stream *standard-output*))
|
||
|
|
(yason:with-output (stream)
|
||
|
|
(yason:with-object ()
|
||
|
|
(yason:encode-object-element "name" (user-name user))
|
||
|
|
(yason:encode-object-element "age" (user-age user)))))
|
||
|
|
#<STANDARD-METHOD YASON:ENCODE (USER) {5B40A591}>
|
||
|
|
CL-USER> (yason:encode (list (make-user :name "horst" :age 27 :password "puppy")
|
||
|
|
(make-user :name "uschi" :age 28 :password "kitten")))
|
||
|
|
[{"name":"horst","age":27},{"name":"uschi","age":28}]
|
||
|
|
(#S(USER :NAME "horst" :AGE 27 :PASSWORD "puppy")
|
||
|
|
#S(USER :NAME "uschi" :AGE 28 :PASSWORD "kitten"))</pre>
|
||
|
|
|
||
|
|
As you can see, the streaming API and the DOM encoder can be
|
||
|
|
used together. <clix:ref>ENCODE</clix:ref> invokes itself
|
||
|
|
recursively, so any application defined method will be called
|
||
|
|
while encoding in-memory objects as appropriate.
|
||
|
|
|
||
|
|
<p>For an example of the interplay between
|
||
|
|
<clix:ref>ENCODE-OBJECT</clix:ref> and
|
||
|
|
<clix:ref>ENCODE-SLOTS</clix:ref>, suppose you have the following
|
||
|
|
CLOS class heirarchy:
|
||
|
|
|
||
|
|
<pre>(defclass shape ()
|
||
|
|
((color :reader color)))
|
||
|
|
|
||
|
|
(defclass square (shape)
|
||
|
|
((side-length :reader side-length)))
|
||
|
|
|
||
|
|
(defclass circle (shape)
|
||
|
|
((radius :reader radius)))</pre>
|
||
|
|
|
||
|
|
In order to implement encoding of circles and squares without
|
||
|
|
duplicating code you can specialize
|
||
|
|
<clix:ref>ENCODE-SLOTS</clix:ref> for all three classes
|
||
|
|
|
||
|
|
<pre>(defmethod yason:encode-slots progn ((shape shape))
|
||
|
|
(yason:encode-object-element "color" (color shape)))
|
||
|
|
|
||
|
|
(defmethod yason:encode-slots progn ((square square))
|
||
|
|
(yason:encode-object-element "side-length" (side-length square)))
|
||
|
|
|
||
|
|
(defmethod yason:encode-slots progn ((circle circle))
|
||
|
|
(yason:encode-object-element "radius" (radius circle)))</pre>
|
||
|
|
|
||
|
|
and then use <clix:ref>ENCODE-OBJECT</clix:ref>:
|
||
|
|
|
||
|
|
<pre>CL-USER> (yason:with-output-to-string* ()
|
||
|
|
(yason:encode-object (make-instance 'square :color "red" :side-length 3)))
|
||
|
|
"{\"color\":\"red\",\"side-length\":3}"
|
||
|
|
CL-USER> (yason:with-output-to-string* ()
|
||
|
|
(yason:encode-object (make-instance 'circle :color "blue" :side-length 5)))
|
||
|
|
"{\"color\":\"blue\",\"radius\":5}"</pre>
|
||
|
|
</p>
|
||
|
|
<p>Alternatively, you can use the shortcut <clix:ref>ENCODE-OBJECT-SLOTS</clix:ref>
|
||
|
|
if you want the keys to be the slot names. For example:
|
||
|
|
<pre>(defclass person ()
|
||
|
|
((name :reader name :initarg :name)
|
||
|
|
(address :reader address :initarg :address)
|
||
|
|
(phone-number :reader phone-number :initarg :phone)
|
||
|
|
(favorite-color :reader favorite-color :initarg :color)))
|
||
|
|
|
||
|
|
(defmethod yason:encode-slots progn ((person person))
|
||
|
|
(yason:encode-object-slots person '(name address phone-number favorite-color)))</pre>
|
||
|
|
and then:
|
||
|
|
|
||
|
|
<pre>CL-USER> (yason:with-output-to-string* ()
|
||
|
|
(yason:encode-object (make-instance 'person :name "John Doe"
|
||
|
|
:address "123 Main St."
|
||
|
|
:phone "(123)-456-7890"
|
||
|
|
:color "blue")))
|
||
|
|
"{\"NAME\":\"John Doe\",\"ADDRESS\":\"123 Main St.\",\"PHONE-NUMBER\":\"(123)-456-7890\",
|
||
|
|
\"FAVORITE-COLOR\":\"blue\"}"</pre>
|
||
|
|
</p>
|
||
|
|
</clix:subchapter>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="index" title="Symbol index">
|
||
|
|
<clix:index/>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="license" title="License">
|
||
|
|
<pre class="none">Copyright (c) 2008-2014 Hans Hübner and contributors
|
||
|
|
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 BKNR 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 THE COPYRIGHT
|
||
|
|
OWNER OR CONTRIBUTORS 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.
|
||
|
|
</pre>
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
<clix:chapter name="ack" title="Acknowledgements">
|
||
|
|
Thanks go to Edi Weitz for being a great inspiration. This
|
||
|
|
documentation as been generated with a hacked-up version of his <a
|
||
|
|
href="http://weitz.de/documentation-template/">DOCUMENTATION-TEMPLATE</a>
|
||
|
|
software. Thanks to David Lichteblau for coining YASON's name.
|
||
|
|
</clix:chapter>
|
||
|
|
|
||
|
|
</clix:documentation>
|