764 lines
39 KiB
HTML
764 lines
39 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Strict//EN">
|
||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta><title>YASON - A JSON encoder/decoder for Common Lisp</title><meta name="description" content="
|
||
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.
|
||
"></meta><style type="text/css">
|
||
body { background-color: #ffffff; max-width: 50em; margin-left: 2em; }
|
||
blockquote { margin-left: 2em; margin-right: 2em; }
|
||
pre { padding:5px; background-color:#e0e0e0 }
|
||
pre.none { padding:5px; background-color:#ffffff }
|
||
h3, h4, h5 { text-decoration: underline; }
|
||
a { text-decoration: none; padding: 1px 2px 1px 2px; }
|
||
a:visited { text-decoration: none; padding: 1px 2px 1px 2px; }
|
||
a:hover { text-decoration: none; padding: 1px 1px 1px 1px; border: 1px solid #000000; }
|
||
a:focus { text-decoration: none; padding: 1px 2px 1px 2px; border: none; }
|
||
a.none { text-decoration: none; padding: 0; }
|
||
a.none:visited { text-decoration: none; padding: 0; }
|
||
a.none:hover { text-decoration: none; border: none; padding: 0; }
|
||
a.none:focus { text-decoration: none; border: none; padding: 0; }
|
||
a.noborder { text-decoration: none; padding: 0; }
|
||
a.noborder:visited { text-decoration: none; padding: 0; }
|
||
a.noborder:hover { text-decoration: none; border: none; padding: 0; }
|
||
a.noborder:focus { text-decoration: none; border: none; padding: 0; }
|
||
</style></head><body>
|
||
|
||
<h1 xmlns="">YASON - A JSON encoder/decoder for Common Lisp</h1>
|
||
|
||
|
||
|
||
<h3 xmlns="">Abstract</h3>
|
||
<blockquote xmlns="">
|
||
YASON is a Common Lisp library for encoding and decoding data in
|
||
the <a xmlns="http://www.w3.org/1999/xhtml" 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.
|
||
</blockquote>
|
||
|
||
<h3 xmlns="">Contents</h3>
|
||
<ol xmlns="">
|
||
<li><a href="#intro">Introduction</a></li>
|
||
<li><a href="#install">Download and Installation</a></li>
|
||
<li><a href="#json-package">Using JSON as package name</a></li>
|
||
<li><a href="#mapping">Mapping between JSON and CL datatypes</a></li>
|
||
<li>
|
||
<a href="#parsing">Parsing JSON data</a><ol><li><a href="#parser-dict">Parser dictionary</a></li></ol>
|
||
</li>
|
||
<li>
|
||
<a href="#encoding">Encoding JSON data</a><ol>
|
||
<li><a href="#dom-encoder">Encoding a JSON DOM</a></li>
|
||
<li><a href="#stream-encoder">Encoding JSON in streaming mode</a></li>
|
||
<li><a href="#app-encoders">Application specific encoders</a></li>
|
||
</ol>
|
||
</li>
|
||
<li><a href="#index">Symbol index</a></li>
|
||
<li><a href="#license">License</a></li>
|
||
<li><a href="#ack">Acknowledgements</a></li>
|
||
</ol>
|
||
|
||
<h3 xmlns=""><a class="none" name="intro">Introduction</a></h3>
|
||
<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>
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="install">Download and Installation</a></h3>
|
||
<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 0.7.6.
|
||
</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>
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="json-package">Using JSON as package name</a></h3>
|
||
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>
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="mapping">Mapping between JSON and CL datatypes</a></h3>
|
||
By default, YASON performs the following mappings between JSON and
|
||
CL datatypes:
|
||
<table border="1">
|
||
<thead>
|
||
<tr>
|
||
<th>JSON<br></br>datatype</th>
|
||
<th>CL<br></br>datatype</th>
|
||
<th>Notes</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>object</td>
|
||
<td>hash-table<br></br>:test #'equal</td>
|
||
<td>
|
||
Keys are strings by default (see
|
||
<code xmlns=""><a href="#*parse-object-key-fn*">*parse-object-key-fn*</a></code>). Set
|
||
<code xmlns=""><a href="#*parse-object-as*">*parse-object-as*</a></code> 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
|
||
<code xmlns=""><a href="#*parse-object-key-fn*">*parse-object-key-fn*</a></code> 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
|
||
<code xmlns=""><a href="#*parse-json-arrays-as-vectors*">*parse-json-arrays-as-vectors*</a></code>).
|
||
</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
|
||
<code xmlns=""><a href="#*parse-json-booleans-as-symbols*">*parse-json-booleans-as-symbols*</a></code>).
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>false</td>
|
||
<td>nil</td>
|
||
<td>
|
||
Can be changed to read as FALSE (see
|
||
<code xmlns=""><a href="#*parse-json-booleans-as-symbols*">*parse-json-booleans-as-symbols*</a></code>).
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>null</td>
|
||
<td>nil</td>
|
||
<td></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="parsing">Parsing JSON data</a></h3>
|
||
<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 <code xmlns=""><a href="#mapping">mapping</a></code>
|
||
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>
|
||
|
||
<h4 xmlns=""><a name="parser-dict">Parser dictionary</a></h4>
|
||
<p xmlns="">[Function]<br><a class="none" name="parse"><b>parse</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">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></i>
|
||
=>
|
||
<i>object</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Parse <code><i>input</i></code>, which must be a string or
|
||
a stream, as JSON. Returns the Lisp representation of the
|
||
JSON structure parsed.
|
||
<p xmlns="http://www.w3.org/1999/xhtml">
|
||
The keyword arguments <code xmlns=""><i>object-key-fn</i></code>,
|
||
<code xmlns=""><i>object-as</i></code>,
|
||
<code xmlns=""><i>json-arrays-as-vectors</i></code>,
|
||
<code xmlns=""><i>json-booleans-as-symbols</i></code>, and
|
||
<code xmlns=""><i>json-null-as-keyword</i></code> may be used
|
||
to specify different values for the parsing parameters
|
||
from the current bindings of the respective special
|
||
variables.
|
||
</p>
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Special variable]<br><a class="none" name="*parse-json-arrays-as-vectors*"><b>*parse-json-arrays-as-vectors*</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
If set to a true value, JSON arrays will be parsed as
|
||
vectors, not as lists. NIL is the default.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Special variable]<br><a class="none" name="*parse-object-as*"><b>*parse-object-as*</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Can be set to <b xmlns="http://www.w3.org/1999/xhtml">:hash-table</b> to parse objects as hash
|
||
tables, <b xmlns="http://www.w3.org/1999/xhtml">:alist</b> to parse them as alists or
|
||
<b xmlns="http://www.w3.org/1999/xhtml">:plist</b> to parse them as plists. <b xmlns="http://www.w3.org/1999/xhtml">:hash-table</b>
|
||
is the default.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Special variable]<br><a class="none" name="*parse-json-booleans-as-symbols*"><b>*parse-json-booleans-as-symbols*</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
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></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Special variable]<br><a class="none" name="*parse-json-null-as-keyword*"><b>*parse-json-null-as-keyword*</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
If set to a true value, JSON null will be read as the
|
||
keyword :NULL, instead of NIL.
|
||
NIL is the default.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Special variable]<br><a class="none" name="*parse-object-key-fn*"><b>*parse-object-key-fn*</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
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></blockquote></p>
|
||
|
||
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="encoding">Encoding JSON data</a></h3>
|
||
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
|
||
<code xmlns=""><a href="#json-output-stream">JSON-OUTPUT-STREAM</a></code> 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
|
||
<code xmlns=""><a href="#make-json-output-stream">MAKE-JSON-OUTPUT-STREAM</a></code> 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>
|
||
|
||
<h4 xmlns=""><a name="dom-encoder">Encoding a JSON DOM</a></h4>
|
||
<p>
|
||
In this mode, an in-memory structure is encoded in JSON
|
||
format. The structure must consist of objects that are
|
||
serializable using the <code xmlns=""><a href="#encode">ENCODE</a></code> function.
|
||
YASON defines a number of encoders for standard data types
|
||
(see <code xmlns=""><a href="#mapping">MAPPING</a></code>), 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>
|
||
|
||
<h4 xmlns=""><a name="dom-encoder-dict">DOM encoder dictionary</a></h4>
|
||
<p xmlns="">[Generic function]<br><a class="none" name="encode"><b>encode</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">object &optional
|
||
stream</clix:lambda-list></i>
|
||
=>
|
||
<i>object</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encode <code><i>object</i></code> in JSON format and
|
||
write to <code><i>stream</i></code>. May be specialized
|
||
by applications to perform specific rendering. Stream
|
||
defaults to *STANDARD-OUTPUT*.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-alist"><b>encode-alist</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">object &optional (stream
|
||
*standard-output*)</clix:lambda-list></i>
|
||
=>
|
||
<i>object</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encodes <code><i>object</i></code>, an alist, in JSON
|
||
format and write to <code><i>stream</i></code>.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-plist"><b>encode-plist</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">object &optional (stream
|
||
*standard-output*)</clix:lambda-list></i>
|
||
=>
|
||
<i>object</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encodes <code><i>object</i></code>, a plist, in JSON
|
||
format and write to <code><i>stream</i></code>.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="make-json-output-stream"><b>make-json-output-stream</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">stream &key (indent t)</clix:lambda-list></i>
|
||
=>
|
||
<i>stream</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Creates a <code><a href="#json-output-stream">json-output-stream</a></code> instance
|
||
that wraps the supplied <code><i>stream</i></code> and
|
||
optionally performs indentation of the generated JSON
|
||
data. The <code><i>indent</i></code> argument is
|
||
described in <code><a href="#with-output">WITH-OUTPUT</a></code>. Note that
|
||
if the <code><i>indent</i></code> argument is NIL, the
|
||
original stream is returned in order to avoid the
|
||
performance penalty of the indentation algorithm.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Special variable]<br><a class="none" name="*list-encoder*"><b>*list-encoder*</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Function to call to translate a CL list into JSON data.
|
||
<code><i>'YASON:ENCODE-PLAIN-LIST-TO-ARRAY</i></code> is the default;
|
||
<code><i>'YASON:ENCODE-PLIST</i></code> and
|
||
<code><i>'YASON:ENCODE-ALIST</i></code> are available to produce
|
||
JSON objects.
|
||
<p xmlns="http://www.w3.org/1999/xhtml">
|
||
This is useful to translate a deeply recursive structure in a single
|
||
<code xmlns=""><i>YASON:ENCODE</i></code> call.
|
||
</p>
|
||
</clix:description></blockquote></p>
|
||
|
||
|
||
<p xmlns="">
|
||
[Special variable]<br><a class="none" name="*symbol-key-encoder*"><b>*symbol-key-encoder*</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Defines the policy to encode symbols as keys (eg. in hash tables).
|
||
The default is to error out, to provide backwards-compatible behaviour.
|
||
<p xmlns="http://www.w3.org/1999/xhtml">
|
||
A useful function that can be bound to this variable is
|
||
<code xmlns=""><a href="#yason:encode-symbol-as-lowercase.">YASON:ENCODE-SYMBOL-AS-LOWERCASE.</a></code>
|
||
</p>
|
||
</clix:description></blockquote></p>
|
||
|
||
|
||
|
||
<h4 xmlns=""><a name="stream-encoder">Encoding JSON in streaming mode</a></h4>
|
||
<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
|
||
<code xmlns=""><a href="#encode">encode</a></code> function, so it is possible to
|
||
intermix the two (see <code xmlns=""><a href="#app-encoders">app-encoders</a></code> 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>
|
||
|
||
<h4 xmlns=""><a name="stream-encoder-dict">Streaming encoder dictionary</a></h4>
|
||
<p xmlns="">[Macro]<br><a class="none" name="with-output"><b>with-output</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">(stream &key indent) &body body</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Set up a JSON streaming encoder context on
|
||
<code><i>stream</i></code>, then evaluate
|
||
<code><i>body</i></code>. <code><i>indent</i></code>
|
||
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></blockquote></p>
|
||
|
||
<p xmlns="">[Macro]<br><a class="none" name="with-output-to-string*"><b>with-output-to-string*</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">(&key indent stream-symbol) &body body</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Set up a JSON streaming encoder context on
|
||
<code><i>stream-symbol</i></code> (by default
|
||
a gensym), then evaluate
|
||
<code><i>body</i></code>. Return a string with the
|
||
generated JSON output. See
|
||
<code><a href="#with-output">WITH-OUTPUT</a></code> for the description of
|
||
the <code><i>indent</i></code> keyword argument.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Condition type]<br><a class="none" name="no-json-output-context"><b>no-json-output-context</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
This condition is signalled when one of the stream
|
||
encoding functions is used outside the dynamic context of
|
||
a <code><a href="#with-output">WITH-OUTPUT</a></code> or
|
||
<code><a href="#with-output-to-string*">WITH-OUTPUT-TO-STRING*</a></code> body.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Macro]<br><a class="none" name="with-array"><b>with-array</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">() &body body</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Open a JSON array, then run <code><i>body</i></code>.
|
||
Inside the body, <code><a href="#encode-array-element">ENCODE-ARRAY-ELEMENT</a></code>
|
||
must be called to encode elements to the opened array.
|
||
Must be called within an existing JSON encoder context
|
||
(see <code><a href="#with-output">WITH-OUTPUT</a></code> and
|
||
<code><a href="#with-output-to-string*">WITH-OUTPUT-TO-STRING*</a></code>).
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-array-element"><b>encode-array-element</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">object</clix:lambda-list></i>
|
||
=>
|
||
<i>object</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encode <code><i>object</i></code> as next array element to
|
||
the last JSON array opened
|
||
with <code><a href="#with-array">WITH-ARRAY</a></code> in the dynamic
|
||
context. <code><i>object</i></code> is encoded using the
|
||
<code><a href="#encode">ENCODE</a></code> generic function, so it must be of
|
||
a type for which an <code><a href="#encode">ENCODE</a></code> method is
|
||
defined.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-array-elements"><b>encode-array-elements</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">&rest objects</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encode <code><i>objects</i></code>, a series of JSON
|
||
encodable objects, as the next array elements in a JSON
|
||
array opened with
|
||
<code><a href="#with-array">WITH-ARRAY</a></code>. ENCODE-ARRAY-ELEMENTS
|
||
uses <code><a href="#encode-array-element">ENCODE-ARRAY-ELEMENT</a></code>, which must
|
||
be applicable to each object in the list
|
||
(i.e. <code><a href="#encode">ENCODE</a></code> must be defined for each
|
||
object type). Additionally, this must be called within a
|
||
valid stream context.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Macro]<br><a class="none" name="with-object"><b>with-object</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">() &body body</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Open a JSON object, then run <code><i>body</i></code>.
|
||
Inside the body,
|
||
<code><a href="#encode-object-element">ENCODE-OBJECT-ELEMENT</a></code> or
|
||
<code><a href="#with-object-element">WITH-OBJECT-ELEMENT</a></code> must be called to
|
||
encode elements to the object. Must be called within an
|
||
existing JSON encoder
|
||
<code><a href="#with-output">WITH-OUTPUT</a></code> and
|
||
<code><a href="#with-output-to-string*">WITH-OUTPUT-TO-STRING*</a></code>.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Macro]<br><a class="none" name="with-object-element"><b>with-object-element</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">(key) &body body</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Open a new encoding context to encode a JSON object
|
||
element. <code><i>key</i></code> is the key of the
|
||
element. The value will be whatever
|
||
<code><i>body</i></code> 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></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-object-element"><b>encode-object-element</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">key value</clix:lambda-list></i>
|
||
=>
|
||
<i>value</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encode <code><i>key</i></code> and
|
||
<code><i>value</i></code> as object element to the last
|
||
JSON object opened with <code><a href="#with-object">WITH-OBJECT</a></code>
|
||
in the dynamic context. <code><i>key</i></code> and
|
||
<code><i>value</i></code> are encoded using the
|
||
<code><a href="#encode">ENCODE</a></code> generic function, so they both
|
||
must be of a type for which an <code><a href="#encode">ENCODE</a></code>
|
||
method is defined.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-object-elements"><b>encode-object-elements</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">&rest elements</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encodes the parameters into JSON in the last object opened
|
||
with <code><a href="#with-object">WITH-OBJECT</a></code> using
|
||
<code><a href="#encode-object-element">ENCODE-OBJECT-ELEMENT</a></code>. The parameters
|
||
should consist of alternating key/value pairs, and this
|
||
must be called within a valid stream context.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-object-slots"><b>encode-object-slots</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">object slots</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Encodes each slot in SLOTS for OBJECT in the last object
|
||
opened with <code><a href="#with-object">WITH-OBJECT</a></code> using
|
||
<code><a href="#encode-object-element">ENCODE-OBJECT-ELEMENT</a></code>. The key is the
|
||
slot name, and the value is the slot value for the slot on
|
||
OBJECT. It is equivalent to
|
||
<pre xmlns="http://www.w3.org/1999/xhtml">(loop for slot in slots
|
||
do (encode-object-element (string slot)
|
||
(slot-value object slot)))
|
||
</pre>
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-slots"><b>encode-slots</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">object</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
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></blockquote></p>
|
||
|
||
<p xmlns="">[Function]<br><a class="none" name="encode-object"><b>encode-object</b> <i><clix:lambda-list xmlns:clix="http://bknr.net/clixdoc">object</clix:lambda-list></i>
|
||
=>
|
||
<i>result*</i></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
Generic function to encode an object. The default implementation
|
||
opens a new object encoding context and calls
|
||
<code><a href="#encode-slots">ENCODE-SLOTS</a></code> on the argument.
|
||
</clix:description></blockquote></p>
|
||
|
||
<p xmlns="">
|
||
[Standard class]<br><a class="none" name="json-output-stream"><b>json-output-stream</b></a><blockquote><clix:description xmlns:clix="http://bknr.net/clixdoc">
|
||
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
|
||
<code><a href="#make-json-output-stream">make-json-output-stream</a></code> to create a
|
||
wrapper stream if required.
|
||
</clix:description></blockquote></p>
|
||
|
||
|
||
|
||
<h4 xmlns=""><a name="app-encoders">Application specific encoders</a></h4>
|
||
|
||
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. <code xmlns=""><a href="#encode">ENCODE</a></code> 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
|
||
<code xmlns=""><a href="#encode-object">ENCODE-OBJECT</a></code> and
|
||
<code xmlns=""><a href="#encode-slots">ENCODE-SLOTS</a></code>, 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
|
||
<code xmlns=""><a href="#encode-slots">ENCODE-SLOTS</a></code> 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 <code xmlns=""><a href="#encode-object">ENCODE-OBJECT</a></code>:
|
||
|
||
<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 <code xmlns=""><a href="#encode-object-slots">ENCODE-OBJECT-SLOTS</a></code>
|
||
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>
|
||
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="index">Symbol index</a></h3>
|
||
<ul xmlns="">
|
||
<li><code><a href="#*list-encoder*">*list-encoder*</a></code></li>
|
||
<li><code><a href="#*parse-json-arrays-as-vectors*">*parse-json-arrays-as-vectors*</a></code></li>
|
||
<li><code><a href="#*parse-json-booleans-as-symbols*">*parse-json-booleans-as-symbols*</a></code></li>
|
||
<li><code><a href="#*parse-json-null-as-keyword*">*parse-json-null-as-keyword*</a></code></li>
|
||
<li><code><a href="#*parse-object-as*">*parse-object-as*</a></code></li>
|
||
<li><code><a href="#*parse-object-key-fn*">*parse-object-key-fn*</a></code></li>
|
||
<li><code><a href="#*symbol-key-encoder*">*symbol-key-encoder*</a></code></li>
|
||
<li><code><a href="#encode">encode</a></code></li>
|
||
<li><code><a href="#encode-alist">encode-alist</a></code></li>
|
||
<li><code><a href="#encode-array-element">encode-array-element</a></code></li>
|
||
<li><code><a href="#encode-array-elements">encode-array-elements</a></code></li>
|
||
<li><code><a href="#encode-object">encode-object</a></code></li>
|
||
<li><code><a href="#encode-object-element">encode-object-element</a></code></li>
|
||
<li><code><a href="#encode-object-elements">encode-object-elements</a></code></li>
|
||
<li><code><a href="#encode-object-slots">encode-object-slots</a></code></li>
|
||
<li><code><a href="#encode-plist">encode-plist</a></code></li>
|
||
<li><code><a href="#encode-slots">encode-slots</a></code></li>
|
||
<li><code><a href="#json-output-stream">json-output-stream</a></code></li>
|
||
<li><code><a href="#make-json-output-stream">make-json-output-stream</a></code></li>
|
||
<li><code><a href="#no-json-output-context">no-json-output-context</a></code></li>
|
||
<li><code><a href="#parse">parse</a></code></li>
|
||
<li><code><a href="#with-array">with-array</a></code></li>
|
||
<li><code><a href="#with-object">with-object</a></code></li>
|
||
<li><code><a href="#with-object-element">with-object-element</a></code></li>
|
||
<li><code><a href="#with-output">with-output</a></code></li>
|
||
<li><code><a href="#with-output-to-string*">with-output-to-string*</a></code></li>
|
||
</ul>
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="license">License</a></h3>
|
||
<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>
|
||
|
||
|
||
<h3 xmlns=""><a class="none" name="ack">Acknowledgements</a></h3>
|
||
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.
|
||
|
||
|
||
</body></html>
|