Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f227483846 | ||
|
|
6cb14dd337 | ||
|
|
fa268b5017 | ||
|
|
a307974731 | ||
|
|
a2381948bb | ||
|
|
5eb2c442a9 | ||
|
|
f471e63bb3 | ||
|
|
bb24697d9b | ||
|
|
c4a7bf90cf | ||
|
|
bef87fc258 | ||
|
|
68d6410da0 | ||
|
|
1e36667193 | ||
|
|
0931d938b0 | ||
|
|
0f2cdd70ff | ||
|
|
5ec101ec21 | ||
|
|
72cd84b92a | ||
|
|
6799c8e4c9 | ||
|
|
8484ea3fb2 | ||
|
|
92d9b89d59 | ||
|
|
bc98ab6a69 | ||
|
|
32cd821273 | ||
|
|
9c6cb42f17 | ||
|
|
ed06777937 | ||
|
|
6362032513 | ||
|
|
84f739036d | ||
|
|
0f358c8eaa | ||
|
|
f0d5c04734 | ||
|
|
fd56deb7dd | ||
|
|
d7ddbf3661 | ||
|
|
b6d0191e51 | ||
|
|
2a70d62251 | ||
|
|
909874b1b9 | ||
|
|
ff57dee13d | ||
|
|
e4cc77ce52 | ||
|
|
889f295958 | ||
|
|
68809cd913 | ||
|
|
910fb92267 | ||
|
|
f241e14cab | ||
|
|
7e9c293986 | ||
|
|
d43464a1ec | ||
|
|
c7079a25eb | ||
|
|
54f38d250c | ||
|
|
c7d543d36c | ||
|
|
7fab57dcef | ||
|
|
cd9757512d | ||
|
|
c0193bfb7f | ||
|
|
6e1f4bb560 | ||
|
|
1358d0bfac | ||
|
|
86d17a8dc2 | ||
|
|
3988ab2d27 | ||
|
|
f7f7bf5ab2 | ||
|
|
a76ba52f34 | ||
|
|
9febdf333c | ||
|
|
013b8b3f60 | ||
|
|
49fc708d4c | ||
|
|
92f6f0f22c | ||
|
|
e9e34f430e | ||
|
|
ab723c7fb5 | ||
|
|
636d5f60f9 | ||
|
|
c3492973e1 | ||
|
|
e20619e071 | ||
|
|
b44e2be032 | ||
|
|
76d6d700ad | ||
|
|
c96763215d | ||
|
|
4a76900bd7 | ||
|
|
1c0a3b2a55 | ||
|
|
056702e541 | ||
|
|
eab23f05d8 | ||
|
|
0944ac8d91 | ||
|
|
a5a43caa9a | ||
|
|
5456fc59ab | ||
|
|
279d8bf108 | ||
|
|
38e35e0973 | ||
|
|
af18578928 | ||
|
|
c30e92603c | ||
|
|
6ecba84817 | ||
|
|
b90ed1accb | ||
|
|
1111960120 | ||
|
|
7f09f48e7e | ||
|
|
b397711a66 |
162
CHANGES
162
CHANGES
@@ -1,3 +1,165 @@
|
||||
Version 2.3
|
||||
===========
|
||||
|
||||
Released 2012-01-27
|
||||
|
||||
* New features:
|
||||
|
||||
- `json_unpack()` and friends: Add support for optional object keys
|
||||
with the ``{s?o}`` syntax.
|
||||
|
||||
- Add `json_object_update_existing()` and
|
||||
`json_object_update_missing()`, for updating only existing keys or
|
||||
only adding missing keys to an object. (#37)
|
||||
|
||||
- Add `json_object_foreach()` for more convenient iteration over
|
||||
objects. (#45, #46)
|
||||
|
||||
- When decoding JSON, write the number of bytes that were read from
|
||||
input to ``error.position`` also on success. This is handy with
|
||||
``JSON_DISABLE_EOF_CHECK``.
|
||||
|
||||
- Add support for decoding any JSON value, not just arrays or
|
||||
objects. The support is enabled with the new ``JSON_DECODE_ANY``
|
||||
flag. Patch by Andrea Marchesini. (#4)
|
||||
|
||||
* Bug fixes
|
||||
|
||||
- Avoid problems with object's serial number growing too big. (#40,
|
||||
#41)
|
||||
|
||||
- Decoding functions now return NULL if the first argument is NULL.
|
||||
Patch by Andrea Marchesini.
|
||||
|
||||
- Include ``jansson_config.h.win32`` in the distribution tarball.
|
||||
|
||||
- Remove ``+`` and leading zeros from exponents in the encoder.
|
||||
(#39)
|
||||
|
||||
- Make Jansson build and work on MinGW. (#39, #38)
|
||||
|
||||
* Documentation
|
||||
|
||||
- Note that the same JSON values must not be encoded in parallel by
|
||||
separate threads. (#42)
|
||||
|
||||
- Document MinGW support.
|
||||
|
||||
|
||||
Version 2.2.1
|
||||
=============
|
||||
|
||||
Released 2011-10-06
|
||||
|
||||
* Bug fixes:
|
||||
|
||||
- Fix real number encoding and decoding under non-C locales. (#32)
|
||||
|
||||
- Fix identifier decoding under non-UTF-8 locales. (#35)
|
||||
|
||||
- `json_load_file()`: Open the input file in binary mode for maximum
|
||||
compatiblity.
|
||||
|
||||
* Documentation:
|
||||
|
||||
- Clarify the lifecycle of the result of the ``s`` fromat of
|
||||
`json_unpack()`. (#31)
|
||||
|
||||
- Add some portability info. (#36)
|
||||
|
||||
- Little clarifications here and there.
|
||||
|
||||
* Other:
|
||||
|
||||
- Some style fixes, issues detected by static analyzers.
|
||||
|
||||
|
||||
Version 2.2
|
||||
===========
|
||||
|
||||
Released 2011-09-03
|
||||
|
||||
* New features:
|
||||
|
||||
- `json_dump_callback()`: Pass the encoder output to a callback
|
||||
function in chunks.
|
||||
|
||||
* Bug fixes:
|
||||
|
||||
- `json_string_set()`: Check that target is a string and value is
|
||||
not NULL.
|
||||
|
||||
* Other:
|
||||
|
||||
- Documentation typo fixes and clarifications.
|
||||
|
||||
|
||||
Version 2.1
|
||||
===========
|
||||
|
||||
Released 2011-06-10
|
||||
|
||||
* New features:
|
||||
|
||||
- `json_loadb()`: Decode a string with a given size, useful if the
|
||||
string is not null terminated.
|
||||
|
||||
- Add ``JSON_ENCODE_ANY`` encoding flag to allow encoding any JSON
|
||||
value. By default, only arrays and objects can be encoded. (#19)
|
||||
|
||||
- Add ``JSON_REJECT_DUPLICATES`` decoding flag to issue a decoding
|
||||
error if any JSON object in the input contins duplicate keys. (#3)
|
||||
|
||||
- Add ``JSON_DISABLE_EOF_CHECK`` decoding flag to stop decoding after a
|
||||
valid JSON input. This allows other data after the JSON data.
|
||||
|
||||
* Bug fixes:
|
||||
|
||||
- Fix an additional memory leak when memory allocation fails in
|
||||
`json_object_set()` and friends.
|
||||
|
||||
- Clear errno before calling `strtod()` for better portability. (#27)
|
||||
|
||||
* Building:
|
||||
|
||||
- Avoid set-but-not-used warning/error in a test. (#20)
|
||||
|
||||
* Other:
|
||||
|
||||
- Minor clarifications to documentation.
|
||||
|
||||
|
||||
Version 2.0.1
|
||||
=============
|
||||
|
||||
Released 2011-03-31
|
||||
|
||||
* Bug fixes:
|
||||
|
||||
- Replace a few `malloc()` and `free()` calls with their
|
||||
counterparts that support custom memory management.
|
||||
|
||||
- Fix object key hashing in json_unpack() strict checking mode.
|
||||
|
||||
- Fix the parentheses in ``JANSSON_VERSION_HEX`` macro.
|
||||
|
||||
- Fix `json_object_size()` return value.
|
||||
|
||||
- Fix a few compilation issues.
|
||||
|
||||
* Portability:
|
||||
|
||||
- Enhance portability of `va_copy()`.
|
||||
|
||||
- Test framework portability enhancements.
|
||||
|
||||
* Documentation:
|
||||
|
||||
- Distribute ``doc/upgrading.rst`` with the source tarball.
|
||||
|
||||
- Build documentation in strict mode in ``make distcheck``.
|
||||
|
||||
|
||||
Version 2.0
|
||||
===========
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
EXTRA_DIST = CHANGES LICENSE README.rst
|
||||
SUBDIRS = doc src test
|
||||
|
||||
check-doc:
|
||||
# "make distcheck" builds the dvi target, so use it to check that the
|
||||
# documentation is built correctly.
|
||||
dvi:
|
||||
$(MAKE) SPHINXOPTS_EXTRA=-W html
|
||||
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
AC_PREREQ([2.60])
|
||||
AC_INIT([jansson], [2.0], [petri@digip.org])
|
||||
AC_INIT([jansson], [2.3], [petri@digip.org])
|
||||
|
||||
AM_INIT_AUTOMAKE([1.10 foreign])
|
||||
|
||||
@@ -14,6 +14,7 @@ AM_CONDITIONAL([GCC], [test x$GCC = xyes])
|
||||
# Checks for libraries.
|
||||
|
||||
# Checks for header files.
|
||||
AC_CHECK_HEADERS([locale.h])
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_TYPE_INT32_T
|
||||
@@ -34,6 +35,12 @@ esac
|
||||
AC_SUBST([json_inline])
|
||||
|
||||
# Checks for library functions.
|
||||
AC_CHECK_FUNCS([setlocale localeconv])
|
||||
case "$ac_cv_header_locale_h$ac_cv_func_localeconv" in
|
||||
yesyes) json_have_localeconv=1;;
|
||||
*) json_have_localeconv=0;;
|
||||
esac
|
||||
AC_SUBST([json_have_localeconv])
|
||||
|
||||
AC_CONFIG_FILES([
|
||||
jansson.pc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
EXTRA_DIST = conf.py apiref.rst changes.rst conformance.rst \
|
||||
gettingstarted.rst github_commits.c index.rst tutorial.rst \
|
||||
ext/refcounting.py
|
||||
gettingstarted.rst github_commits.c index.rst portability.rst \
|
||||
tutorial.rst upgrading.rst ext/refcounting.py
|
||||
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXOPTS = -d _build/doctrees $(SPHINXOPTS_EXTRA)
|
||||
|
||||
264
doc/apiref.rst
264
doc/apiref.rst
@@ -481,12 +481,13 @@ A JSON array is an ordered collection of other JSON values.
|
||||
|
||||
Removes the element in *array* at position *index*, shifting the
|
||||
elements after *index* one position towards the start of the array.
|
||||
Returns 0 on success and -1 on error.
|
||||
Returns 0 on success and -1 on error. The reference count of the
|
||||
removed value is decremented.
|
||||
|
||||
.. function:: int json_array_clear(json_t *array)
|
||||
|
||||
Removes all elements from *array*. Returns 0 on sucess and -1 on
|
||||
error.
|
||||
error. The reference count of all removed values are decremented.
|
||||
|
||||
.. function:: int json_array_extend(json_t *array, json_t *other_array)
|
||||
|
||||
@@ -549,21 +550,70 @@ Unicode string and the value is any JSON value.
|
||||
.. function:: int json_object_del(json_t *object, const char *key)
|
||||
|
||||
Delete *key* from *object* if it exists. Returns 0 on success, or
|
||||
-1 if *key* was not found.
|
||||
|
||||
-1 if *key* was not found. The reference count of the removed value
|
||||
is decremented.
|
||||
|
||||
.. function:: int json_object_clear(json_t *object)
|
||||
|
||||
Remove all elements from *object*. Returns 0 on success and -1 if
|
||||
*object* is not a JSON object.
|
||||
*object* is not a JSON object. The reference count of all removed
|
||||
values are decremented.
|
||||
|
||||
.. function:: int json_object_update(json_t *object, json_t *other)
|
||||
|
||||
Update *object* with the key-value pairs from *other*, overwriting
|
||||
existing keys. Returns 0 on success or -1 on error.
|
||||
|
||||
.. function:: int json_object_update_existing(json_t *object, json_t *other)
|
||||
|
||||
The following functions implement an iteration protocol for objects:
|
||||
Like :func:`json_object_update()`, but only the values of existing
|
||||
keys are updated. No new keys are created. Returns 0 on success or
|
||||
-1 on error.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
.. function:: int json_object_update_missing(json_t *object, json_t *other)
|
||||
|
||||
Like :func:`json_object_update()`, but only new keys are created.
|
||||
The value of any existing key is not changed. Returns 0 on success
|
||||
or -1 on error.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
The following macro can be used to iterate through all key-value pairs
|
||||
in an object.
|
||||
|
||||
.. function:: json_object_foreach(object, key, value)
|
||||
|
||||
Iterate over every key-value pair of ``object``, running the block
|
||||
of code that follows each time with the proper values set to
|
||||
variables ``key`` and ``value``, of types :type:`const char *` and
|
||||
:type:`json_t *` respectively. Example::
|
||||
|
||||
/* obj is a JSON object */
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
json_object_foreach(obj, key, value) {
|
||||
/* block of code that uses key and value */
|
||||
}
|
||||
|
||||
The items are not returned in any particular order.
|
||||
|
||||
This macro expands to an ordinary ``for`` statement upon
|
||||
preprocessing, so its performance is equivalent to that of
|
||||
hand-written iteration code using the object iteration protocol
|
||||
(see below). The main advantage of this macro is that it abstracts
|
||||
away the complexity behind iteration, and makes for shorter, more
|
||||
concise code.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
|
||||
The following functions implement an iteration protocol for objects,
|
||||
allowing to iterate through all key-value pairs in an object. The
|
||||
items are not returned in any particular order, as this would require
|
||||
sorting due to the internal hashtable implementation.
|
||||
|
||||
.. function:: void *json_object_iter(json_t *object)
|
||||
|
||||
@@ -605,11 +655,21 @@ The following functions implement an iteration protocol for objects:
|
||||
*value*. This is useful when *value* is newly created and not used
|
||||
after the call.
|
||||
|
||||
.. function:: void *json_object_key_to_iter(const char *key)
|
||||
|
||||
Like :func:`json_object_iter_at()`, but much faster. Only works for
|
||||
values returned by :func:`json_object_iter_key()`. Using other keys
|
||||
will lead to segfaults. This function is used internally to
|
||||
implement :func:`json_object_foreach`.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
The iteration protocol can be used for example as follows::
|
||||
|
||||
/* obj is a JSON object */
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
void *iter = json_object_iter(obj);
|
||||
while(iter)
|
||||
{
|
||||
@@ -670,7 +730,9 @@ and pass a pointer to a function. Example::
|
||||
}
|
||||
|
||||
Also note that if the call succeeded (``json != NULL`` in the above
|
||||
example), the contents of ``error`` are unspecified.
|
||||
example), the contents of ``error`` are generally left unspecified.
|
||||
The decoding functions write to the ``position`` member also on
|
||||
success. See :ref:`apiref-decoding` for more info.
|
||||
|
||||
All functions also accept *NULL* as the :type:`json_error_t` pointer,
|
||||
in which case no error information is returned to the caller.
|
||||
@@ -680,8 +742,10 @@ Encoding
|
||||
========
|
||||
|
||||
This sections describes the functions that can be used to encode
|
||||
values to JSON. Only objects and arrays can be encoded, since they are
|
||||
the only valid "root" values of a JSON text.
|
||||
values to JSON. By default, only objects and arrays can be encoded
|
||||
directly, since they are the only valid *root* values of a JSON text.
|
||||
To encode any JSON value, use the ``JSON_ENCODE_ANY`` flag (see
|
||||
below).
|
||||
|
||||
By default, the output has no newlines, and spaces are used between
|
||||
array and object elements for a readable output. This behavior can be
|
||||
@@ -696,9 +760,9 @@ can be ORed together to obtain *flags*.
|
||||
``JSON_INDENT(n)``
|
||||
Pretty-print the result, using newlines between array and object
|
||||
items, and indenting with *n* spaces. The valid range for *n* is
|
||||
between 0 and 32, other values result in an undefined output. If
|
||||
``JSON_INDENT`` is not used or *n* is 0, no newlines are inserted
|
||||
between array and object items.
|
||||
between 0 and 31 (inclusive), other values result in an undefined
|
||||
output. If ``JSON_INDENT`` is not used or *n* is 0, no newlines are
|
||||
inserted between array and object items.
|
||||
|
||||
``JSON_COMPACT``
|
||||
This flag enables a compact representation, i.e. sets the separator
|
||||
@@ -722,6 +786,18 @@ can be ORed together to obtain *flags*.
|
||||
example, decoding a JSON text and then encoding with this flag
|
||||
preserves the order of object keys.
|
||||
|
||||
``JSON_ENCODE_ANY``
|
||||
Specifying this flag makes it possible to encode any JSON value on
|
||||
its own. Without it, only objects and arrays can be passed as the
|
||||
*root* value to the encoding functions.
|
||||
|
||||
**Note:** Encoding any value may be useful in some scenarios, but
|
||||
it's generally discouraged as it violates strict compatiblity with
|
||||
:rfc:`4627`. If you use this flag, don't expect interoperatibility
|
||||
with other JSON systems.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
The following functions perform the actual JSON encoding. The result
|
||||
is in UTF-8.
|
||||
|
||||
@@ -745,6 +821,30 @@ is in UTF-8.
|
||||
*path* already exists, it is overwritten. *flags* is described
|
||||
above. Returns 0 on success and -1 on error.
|
||||
|
||||
.. type:: json_dump_callback_t
|
||||
|
||||
A typedef for a function that's called by
|
||||
:func:`json_dump_callback()`::
|
||||
|
||||
typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data);
|
||||
|
||||
*buffer* points to a buffer containing a chunk of output, *size* is
|
||||
the length of the buffer, and *data* is the corresponding
|
||||
:func:`json_dump_callback()` argument passed through.
|
||||
|
||||
On error, the function should return -1 to stop the encoding
|
||||
process. On success, it should return 0.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
.. function:: int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags)
|
||||
|
||||
Call *callback* repeatedly, passing a chunk of the JSON
|
||||
representation of *root* each time. *flags* is described above.
|
||||
Returns 0 on success and -1 on error.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
|
||||
.. _apiref-decoding:
|
||||
|
||||
@@ -756,20 +856,88 @@ text to the Jansson representation of JSON data. The JSON
|
||||
specification requires that a JSON text is either a serialized array
|
||||
or object, and this requirement is also enforced with the following
|
||||
functions. In other words, the top level value in the JSON text being
|
||||
decoded must be either array or object.
|
||||
decoded must be either array or object. To decode any JSON value, use
|
||||
the ``JSON_DECODE_ANY`` flag (see below).
|
||||
|
||||
See :ref:`rfc-conformance` for a discussion on Jansson's conformance
|
||||
to the JSON specification. It explains many design decisions that
|
||||
affect especially the behavior of the decoder.
|
||||
|
||||
Each function takes a *flags* parameter that can be used to control
|
||||
the behavior of the decoder. Its default value is 0. The following
|
||||
macros can be ORed together to obtain *flags*.
|
||||
|
||||
``JSON_REJECT_DUPLICATES``
|
||||
Issue a decoding error if any JSON object in the input text
|
||||
contains duplicate keys. Without this flag, the value of the last
|
||||
occurence of each key ends up in the result. Key equivalence is
|
||||
checked byte-by-byte, without special Unicode comparison
|
||||
algorithms.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
``JSON_DECODE_ANY``
|
||||
By default, the decoder expects an array or object as the input.
|
||||
With this flag enabled, the decoder accepts any valid JSON value.
|
||||
|
||||
**Note:** Decoding any value may be useful in some scenarios, but
|
||||
it's generally discouraged as it violates strict compatiblity with
|
||||
:rfc:`4627`. If you use this flag, don't expect interoperatibility
|
||||
with other JSON systems.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
``JSON_DISABLE_EOF_CHECK``
|
||||
By default, the decoder expects that its whole input constitutes a
|
||||
valid JSON text, and issues an error if there's extra data after
|
||||
the otherwise valid JSON input. With this flag enabled, the decoder
|
||||
stops after decoding a valid JSON array or object, and thus allows
|
||||
extra data after the JSON text.
|
||||
|
||||
Normally, reading will stop when the last ``]`` or ``}`` in the
|
||||
JSON input is encountered. If both ``JSON_DISABLE_EOF_CHECK`` and
|
||||
``JSON_DECODE_ANY`` flags are used, the decoder may read one extra
|
||||
UTF-8 code unit (up to 4 bytes of input). For example, decoding
|
||||
``4true`` correctly decodes the integer 4, but also reads the
|
||||
``t``. For this reason, if reading multiple consecutive values that
|
||||
are not arrays or objects, they should be separated by at least one
|
||||
whitespace character.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
Each function also takes an optional :type:`json_error_t` parameter
|
||||
that is filled with error information if decoding fails. It's also
|
||||
updated on success; the number of bytes of input read is written to
|
||||
its ``position`` field. This is especially useful when using
|
||||
``JSON_DISABLE_EOF_CHECK`` to read multiple consecutive JSON texts.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
Number of bytes of input read is written to the ``position`` field
|
||||
of the :type:`json_error_t` structure.
|
||||
|
||||
If no error or position information is needed, you can pass *NULL*.
|
||||
|
||||
The following functions perform the actual JSON decoding.
|
||||
|
||||
.. function:: json_t *json_loads(const char *input, size_t flags, json_error_t *error)
|
||||
|
||||
.. refcounting:: new
|
||||
|
||||
Decodes the JSON string *input* and returns the array or object it
|
||||
contains, or *NULL* on error, in which case *error* is filled with
|
||||
information about the error. *flags* is currently unused, and
|
||||
should be set to 0.
|
||||
information about the error. *flags* is described above.
|
||||
|
||||
.. function:: json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error)
|
||||
|
||||
.. refcounting:: new
|
||||
|
||||
Decodes the JSON string *buffer*, whose length is *buflen*, and
|
||||
returns the array or object it contains, or *NULL* on error, in
|
||||
which case *error* is filled with information about the error. This
|
||||
is similar to :func:`json_loads()` except that the string doesn't
|
||||
need to be null-terminated. *flags* is described above.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
.. function:: json_t *json_loadf(FILE *input, size_t flags, json_error_t *error)
|
||||
|
||||
@@ -777,8 +945,18 @@ affect especially the behavior of the decoder.
|
||||
|
||||
Decodes the JSON text in stream *input* and returns the array or
|
||||
object it contains, or *NULL* on error, in which case *error* is
|
||||
filled with information about the error. *flags* is currently
|
||||
unused, and should be set to 0.
|
||||
filled with information about the error. *flags* is described
|
||||
above.
|
||||
|
||||
This function will start reading the input from whatever position
|
||||
the input file was, without attempting to seek first. If an error
|
||||
occurs, the file position will be left indeterminate. On success,
|
||||
the file position will be at EOF, unless ``JSON_DISABLE_EOF_CHECK``
|
||||
flag was used. In this case, the file position will be at the first
|
||||
character after the last ``]`` or ``}`` in the JSON input. This
|
||||
allows calling :func:`json_loadf()` on the same ``FILE`` object
|
||||
multiple times, if the input consists of consecutive JSON texts,
|
||||
possibly separated by whitespace.
|
||||
|
||||
.. function:: json_t *json_load_file(const char *path, size_t flags, json_error_t *error)
|
||||
|
||||
@@ -786,8 +964,8 @@ affect especially the behavior of the decoder.
|
||||
|
||||
Decodes the JSON text in file *path* and returns the array or
|
||||
object it contains, or *NULL* on error, in which case *error* is
|
||||
filled with information about the error. *flags* is currently
|
||||
unused, and should be set to 0.
|
||||
filled with information about the error. *flags* is described
|
||||
above.
|
||||
|
||||
|
||||
.. _apiref-pack:
|
||||
@@ -795,14 +973,14 @@ affect especially the behavior of the decoder.
|
||||
Building Values
|
||||
===============
|
||||
|
||||
This sectinon describes functions that help to create, or *pack*,
|
||||
This section describes functions that help to create, or *pack*,
|
||||
complex JSON values, especially nested objects and arrays. Value
|
||||
building is based on a *format string* that is used to tell the
|
||||
functions about the expected arguments.
|
||||
|
||||
For example, the format string ``"i"`` specifies a single integer
|
||||
value, while the format string ``"[ssb]"`` or the equivalent ``"[s, s,
|
||||
b]"`` specifies an array value with two integers and a boolean as its
|
||||
b]"`` specifies an array value with two strings and a boolean as its
|
||||
items::
|
||||
|
||||
/* Create the JSON integer 42 */
|
||||
@@ -837,7 +1015,7 @@ denotes the C type that is expected as the corresponding argument.
|
||||
``o`` (any value) [json_t \*]
|
||||
Output any given JSON value as-is. If the value is added to an
|
||||
array or object, the reference to the value passed to ``o`` is
|
||||
stealed by the container.
|
||||
stolen by the container.
|
||||
|
||||
``O`` (any value) [json_t \*]
|
||||
Like ``o``, but the argument's reference count is incremented.
|
||||
@@ -857,6 +1035,8 @@ denotes the C type that is expected as the corresponding argument.
|
||||
fourth, etc. format character represent a value. Any value may be
|
||||
an object or array, i.e. recursive value building is supported.
|
||||
|
||||
Whitespace, ``:`` and ``,`` are ignored.
|
||||
|
||||
The following functions compose the value building API:
|
||||
|
||||
.. function:: json_t *json_pack(const char *fmt, ...)
|
||||
@@ -887,10 +1067,10 @@ More examples::
|
||||
json_pack("{}");
|
||||
|
||||
/* Build the JSON object {"foo": 42, "bar": 7} */
|
||||
json_pack("{sisb}", "foo", 42, "bar", 7);
|
||||
json_pack("{sisi}", "foo", 42, "bar", 7);
|
||||
|
||||
/* Like above, ':', ',' and whitespace are ignored */
|
||||
json_pack("{s:i, s:b}", "foo", 42, "bar", 7);
|
||||
json_pack("{s:i, s:i}", "foo", 42, "bar", 7);
|
||||
|
||||
/* Build the JSON array [[1, 2], {"cool": true}] */
|
||||
json_pack("[[i,i],{s:b]]", 1, 2, "cool", 1);
|
||||
@@ -918,7 +1098,9 @@ type whose address should be passed.
|
||||
|
||||
``s`` (string) [const char \*]
|
||||
Convert a JSON string to a pointer to a NULL terminated UTF-8
|
||||
string.
|
||||
string. The resulting string is extracted by using
|
||||
:func:`json_string_value()` internally, so it exists as long as
|
||||
there are still references to the corresponding JSON string.
|
||||
|
||||
``n`` (null)
|
||||
Expect a JSON null value. Nothing is extracted.
|
||||
@@ -962,6 +1144,11 @@ type whose address should be passed.
|
||||
``fmt`` may contain objects and arrays as values, i.e. recursive
|
||||
value extraction is supporetd.
|
||||
|
||||
.. versionadded:: 2.3
|
||||
Any ``s`` representing a key may be suffixed with a ``?`` to
|
||||
make the key optional. If the key is not found, nothing is
|
||||
extracted. See below for an example.
|
||||
|
||||
``!``
|
||||
This special format character is used to enable the check that
|
||||
all object and array items are accessed, on a per-value basis. It
|
||||
@@ -976,6 +1163,8 @@ type whose address should be passed.
|
||||
or object as the last format character before the closing bracket
|
||||
or brace.
|
||||
|
||||
Whitespace, ``:`` and ``,`` are ignored.
|
||||
|
||||
The following functions compose the parsing and validation API:
|
||||
|
||||
.. function:: int json_unpack(json_t *root, const char *fmt, ...)
|
||||
@@ -992,6 +1181,20 @@ The following functions compose the parsing and validation API:
|
||||
behaviour of the unpacker, see below for the flags. Returns 0 on
|
||||
success and -1 on failure.
|
||||
|
||||
.. note::
|
||||
|
||||
The first argument of all unpack functions is ``json_t *root``
|
||||
instead of ``const json_t *root``, because the use of ``O`` format
|
||||
character causes the reference count of ``root``, or some value
|
||||
reachable from ``root``, to be increased. Furthermore, the ``o``
|
||||
format character may be used to extract a value as-is, which allows
|
||||
modifying the structure or contents of a value reachable from
|
||||
``root``.
|
||||
|
||||
If the ``O`` and ``o`` format character are not used, it's
|
||||
perfectly safe to cast a ``const json_t *`` variable to plain
|
||||
``json_t *`` when used with these functions.
|
||||
|
||||
The following unpacking flags are available:
|
||||
|
||||
``JSON_STRICT``
|
||||
@@ -1028,6 +1231,13 @@ Examples::
|
||||
json_unpack(root, "[ii!]", &myint1, &myint2);
|
||||
/* returns -1 for failed validation */
|
||||
|
||||
/* root is an empty JSON object */
|
||||
int myint = 0, myint2 = 0;
|
||||
json_unpack(root, "{s?i, s?[ii]}",
|
||||
"foo", &myint1,
|
||||
"bar", &myint2, &myint3);
|
||||
/* myint1, myint2 or myint3 is no touched as "foo" and "bar" don't exist */
|
||||
|
||||
|
||||
Equality
|
||||
========
|
||||
@@ -1096,6 +1306,8 @@ copied in a recursive fashion.
|
||||
Returns a deep copy of *value*, or *NULL* on error.
|
||||
|
||||
|
||||
.. _apiref-custom-memory-allocation:
|
||||
|
||||
Custom Memory Allocation
|
||||
========================
|
||||
|
||||
@@ -1164,5 +1376,5 @@ JSON structures by zeroing all memory when freed::
|
||||
For more information about the issues of storing sensitive data in
|
||||
memory, see
|
||||
http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/protect-secrets.html.
|
||||
The page also examplains the :func:`guaranteed_memset()` function used
|
||||
The page also explains the :func:`guaranteed_memset()` function used
|
||||
in the example and gives a sample implementation for it.
|
||||
|
||||
@@ -48,9 +48,9 @@ copyright = u'2009-2011, Petri Lehtinen'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '2.0'
|
||||
version = '2.3'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '2.0'
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -5,8 +5,7 @@ RFC Conformance
|
||||
***************
|
||||
|
||||
JSON is specified in :rfc:`4627`, *"The application/json Media Type
|
||||
for JavaScript Object Notation (JSON)"*. This chapter discusses
|
||||
Jansson's conformance to this specification.
|
||||
for JavaScript Object Notation (JSON)"*.
|
||||
|
||||
Character Encoding
|
||||
==================
|
||||
@@ -30,6 +29,12 @@ error::
|
||||
|
||||
All other Unicode codepoints U+0001 through U+10FFFF are allowed.
|
||||
|
||||
Unicode normalization or any other transformation is never performed
|
||||
on any strings (string values or object keys). When checking for
|
||||
equivalence of strings or object keys, the comparison is performed
|
||||
byte by byte between the original UTF-8 representations of the
|
||||
strings.
|
||||
|
||||
Numbers
|
||||
=======
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ Compiling and Installing Jansson
|
||||
The Jansson source is available at
|
||||
http://www.digip.org/jansson/releases/.
|
||||
|
||||
Unix-like systems
|
||||
-----------------
|
||||
Unix-like systems (including MinGW)
|
||||
-----------------------------------
|
||||
|
||||
Unpack the source tarball and change to the source directory:
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ data. Its main features and design principles are:
|
||||
Jansson is licensed under the `MIT license`_; see LICENSE in the
|
||||
source distribution for details.
|
||||
|
||||
Jansson is used in production and its API is stable. It works on
|
||||
numerous platforms, including numerous Unix like systems and Windows.
|
||||
It's suitable for use on any system, including desktop, server, and
|
||||
small embedded systems.
|
||||
|
||||
|
||||
.. _`MIT license`: http://www.opensource.org/licenses/mit-license.php
|
||||
.. _Jansson: http://www.digip.org/jansson/
|
||||
@@ -36,6 +41,7 @@ Contents
|
||||
upgrading
|
||||
tutorial
|
||||
conformance
|
||||
portability
|
||||
apiref
|
||||
changes
|
||||
|
||||
|
||||
52
doc/portability.rst
Normal file
52
doc/portability.rst
Normal file
@@ -0,0 +1,52 @@
|
||||
***********
|
||||
Portability
|
||||
***********
|
||||
|
||||
Thread safety
|
||||
-------------
|
||||
|
||||
Jansson is thread safe and has no mutable global state. The only
|
||||
exception are the memory allocation functions, that should be set at
|
||||
most once, and only on program startup. See
|
||||
:ref:`apiref-custom-memory-allocation`.
|
||||
|
||||
There's no locking performed inside Jansson's code, so a multithreaded
|
||||
program must perform its own locking if JSON values are shared by
|
||||
multiple threads. Jansson's reference counting semantics may make this
|
||||
a bit harder than it seems, as it's possible to have a reference to a
|
||||
value that's also stored inside a list or object. Modifying the
|
||||
container (adding or removing values) may trigger concurrent access to
|
||||
such values, as containers manage the reference count of their
|
||||
contained values. Bugs involving concurrent incrementing or
|
||||
decrementing of deference counts may be hard to track.
|
||||
|
||||
The encoding functions (:func:`json_dumps()` and friends) track
|
||||
reference loops by modifying the internal state of objects and arrays.
|
||||
For this reason, encoding functions must not be run on the same JSON
|
||||
values in two separate threads at the same time. As already noted
|
||||
above, be especially careful if two arrays or objects share their
|
||||
contained values with another array or object.
|
||||
|
||||
If you want to make sure that two JSON value hierarchies do not
|
||||
contain shared values, use :func:`json_deep_copy()` to make copies.
|
||||
|
||||
Locale
|
||||
------
|
||||
|
||||
Jansson works fine under any locale.
|
||||
|
||||
However, if the host program is multithreaded and uses ``setlocale()``
|
||||
to switch the locale in one thread while Jansson is currently encoding
|
||||
or decoding JSON data in another thread, the result may be wrong or
|
||||
the program may even crash.
|
||||
|
||||
Jansson uses locale specific functions for certain string conversions
|
||||
in the encoder and decoder, and then converts the locale specific
|
||||
values to/from the JSON representation. This fails if the locale
|
||||
changes between the string conversion and the locale-to-JSON
|
||||
conversion. This can only happen in multithreaded programs that use
|
||||
``setlocale()``, because ``setlocale()`` switches the locale for all
|
||||
running threads, not only the thread that calls ``setlocale()``.
|
||||
|
||||
If your program uses ``setlocale()`` as described above, consider
|
||||
using the thread-safe ``uselocale()`` instead.
|
||||
@@ -1,3 +1,5 @@
|
||||
EXTRA_DIST = jansson_config.h.win32
|
||||
|
||||
include_HEADERS = jansson.h jansson_config.h
|
||||
|
||||
lib_LTLIBRARIES = libjansson.la
|
||||
@@ -12,14 +14,15 @@ libjansson_la_SOURCES = \
|
||||
pack_unpack.c \
|
||||
strbuffer.c \
|
||||
strbuffer.h \
|
||||
strconv.c \
|
||||
utf.c \
|
||||
utf.h \
|
||||
value.c
|
||||
libjansson_la_LDFLAGS = \
|
||||
-export-symbols-regex '^json_' \
|
||||
-version-info 4:0:0
|
||||
-version-info 7:0:3
|
||||
|
||||
if GCC
|
||||
# These flags are gcc specific
|
||||
AM_CFLAGS = -Wall -Wextra -Werror
|
||||
AM_CFLAGS = -Wall -Wextra -Wdeclaration-after-statement -Werror
|
||||
endif
|
||||
|
||||
90
src/dump.c
90
src/dump.c
@@ -19,21 +19,17 @@
|
||||
#define MAX_INTEGER_STR_LENGTH 100
|
||||
#define MAX_REAL_STR_LENGTH 100
|
||||
|
||||
typedef int (*dump_func)(const char *buffer, int size, void *data);
|
||||
|
||||
struct string
|
||||
{
|
||||
char *buffer;
|
||||
int length;
|
||||
int size;
|
||||
struct object_key {
|
||||
size_t serial;
|
||||
const char *key;
|
||||
};
|
||||
|
||||
static int dump_to_strbuffer(const char *buffer, int size, void *data)
|
||||
static int dump_to_strbuffer(const char *buffer, size_t size, void *data)
|
||||
{
|
||||
return strbuffer_append_bytes((strbuffer_t *)data, buffer, size);
|
||||
}
|
||||
|
||||
static int dump_to_file(const char *buffer, int size, void *data)
|
||||
static int dump_to_file(const char *buffer, size_t size, void *data)
|
||||
{
|
||||
FILE *dest = (FILE *)data;
|
||||
if(fwrite(buffer, size, 1, dest) != 1)
|
||||
@@ -44,7 +40,7 @@ static int dump_to_file(const char *buffer, int size, void *data)
|
||||
/* 32 spaces (the maximum indentation size) */
|
||||
static char whitespace[] = " ";
|
||||
|
||||
static int dump_indent(size_t flags, int depth, int space, dump_func dump, void *data)
|
||||
static int dump_indent(size_t flags, int depth, int space, json_dump_callback_t dump, void *data)
|
||||
{
|
||||
if(JSON_INDENT(flags) > 0)
|
||||
{
|
||||
@@ -66,7 +62,7 @@ static int dump_indent(size_t flags, int depth, int space, dump_func dump, void
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dump_string(const char *str, int ascii, dump_func dump, void *data)
|
||||
static int dump_string(const char *str, int ascii, json_dump_callback_t dump, void *data)
|
||||
{
|
||||
const char *pos, *end;
|
||||
int32_t codepoint;
|
||||
@@ -155,18 +151,20 @@ static int dump_string(const char *str, int ascii, dump_func dump, void *data)
|
||||
|
||||
static int object_key_compare_keys(const void *key1, const void *key2)
|
||||
{
|
||||
return strcmp((*(const object_key_t **)key1)->key,
|
||||
(*(const object_key_t **)key2)->key);
|
||||
return strcmp(((const struct object_key *)key1)->key,
|
||||
((const struct object_key *)key2)->key);
|
||||
}
|
||||
|
||||
static int object_key_compare_serials(const void *key1, const void *key2)
|
||||
{
|
||||
return (*(const object_key_t **)key1)->serial -
|
||||
(*(const object_key_t **)key2)->serial;
|
||||
size_t a = ((const struct object_key *)key1)->serial;
|
||||
size_t b = ((const struct object_key *)key2)->serial;
|
||||
|
||||
return a < b ? -1 : a == b ? 0 : 1;
|
||||
}
|
||||
|
||||
static int do_dump(const json_t *json, size_t flags, int depth,
|
||||
dump_func dump, void *data)
|
||||
json_dump_callback_t dump, void *data)
|
||||
{
|
||||
int ascii = flags & JSON_ENSURE_ASCII ? 1 : 0;
|
||||
|
||||
@@ -198,26 +196,12 @@ static int do_dump(const json_t *json, size_t flags, int depth,
|
||||
{
|
||||
char buffer[MAX_REAL_STR_LENGTH];
|
||||
int size;
|
||||
double value = json_real_value(json);
|
||||
|
||||
size = snprintf(buffer, MAX_REAL_STR_LENGTH, "%.17g",
|
||||
json_real_value(json));
|
||||
if(size >= MAX_REAL_STR_LENGTH)
|
||||
size = jsonp_dtostr(buffer, MAX_REAL_STR_LENGTH, value);
|
||||
if(size < 0)
|
||||
return -1;
|
||||
|
||||
/* Make sure there's a dot or 'e' in the output. Otherwise
|
||||
a real is converted to an integer when decoding */
|
||||
if(strchr(buffer, '.') == NULL &&
|
||||
strchr(buffer, 'e') == NULL)
|
||||
{
|
||||
if(size + 2 >= MAX_REAL_STR_LENGTH) {
|
||||
/* No space to append ".0" */
|
||||
return -1;
|
||||
}
|
||||
buffer[size] = '.';
|
||||
buffer[size + 1] = '0';
|
||||
size += 2;
|
||||
}
|
||||
|
||||
return dump(buffer, size, data);
|
||||
}
|
||||
|
||||
@@ -308,19 +292,20 @@ static int do_dump(const json_t *json, size_t flags, int depth,
|
||||
|
||||
if(flags & JSON_SORT_KEYS || flags & JSON_PRESERVE_ORDER)
|
||||
{
|
||||
const object_key_t **keys;
|
||||
struct object_key *keys;
|
||||
size_t size, i;
|
||||
int (*cmp_func)(const void *, const void *);
|
||||
|
||||
size = json_object_size(json);
|
||||
keys = jsonp_malloc(size * sizeof(object_key_t *));
|
||||
keys = jsonp_malloc(size * sizeof(struct object_key));
|
||||
if(!keys)
|
||||
goto object_error;
|
||||
|
||||
i = 0;
|
||||
while(iter)
|
||||
{
|
||||
keys[i] = jsonp_object_iter_fullkey(iter);
|
||||
keys[i].serial = hashtable_iter_serial(iter);
|
||||
keys[i].key = json_object_iter_key(iter);
|
||||
iter = json_object_iter_next((json_t *)json, iter);
|
||||
i++;
|
||||
}
|
||||
@@ -331,14 +316,14 @@ static int do_dump(const json_t *json, size_t flags, int depth,
|
||||
else
|
||||
cmp_func = object_key_compare_serials;
|
||||
|
||||
qsort(keys, size, sizeof(object_key_t *), cmp_func);
|
||||
qsort(keys, size, sizeof(struct object_key), cmp_func);
|
||||
|
||||
for(i = 0; i < size; i++)
|
||||
{
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
key = keys[i]->key;
|
||||
key = keys[i].key;
|
||||
value = json_object_get(json, key);
|
||||
assert(value);
|
||||
|
||||
@@ -415,35 +400,26 @@ static int do_dump(const json_t *json, size_t flags, int depth,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char *json_dumps(const json_t *json, size_t flags)
|
||||
{
|
||||
strbuffer_t strbuff;
|
||||
char *result;
|
||||
|
||||
if(!json_is_array(json) && !json_is_object(json))
|
||||
return NULL;
|
||||
|
||||
if(strbuffer_init(&strbuff))
|
||||
return NULL;
|
||||
|
||||
if(do_dump(json, flags, 0, dump_to_strbuffer, (void *)&strbuff)) {
|
||||
strbuffer_close(&strbuff);
|
||||
return NULL;
|
||||
}
|
||||
if(json_dump_callback(json, dump_to_strbuffer, (void *)&strbuff, flags))
|
||||
result = NULL;
|
||||
else
|
||||
result = jsonp_strdup(strbuffer_value(&strbuff));
|
||||
|
||||
result = jsonp_strdup(strbuffer_value(&strbuff));
|
||||
strbuffer_close(&strbuff);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int json_dumpf(const json_t *json, FILE *output, size_t flags)
|
||||
{
|
||||
if(!json_is_array(json) && !json_is_object(json))
|
||||
return -1;
|
||||
|
||||
return do_dump(json, flags, 0, dump_to_file, (void *)output);
|
||||
return json_dump_callback(json, dump_to_file, (void *)output, flags);
|
||||
}
|
||||
|
||||
int json_dump_file(const json_t *json, const char *path, size_t flags)
|
||||
@@ -459,3 +435,13 @@ int json_dump_file(const json_t *json, const char *path, size_t flags)
|
||||
fclose(output);
|
||||
return result;
|
||||
}
|
||||
|
||||
int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags)
|
||||
{
|
||||
if(!(flags & JSON_ENCODE_ANY)) {
|
||||
if(!json_is_array(json) && !json_is_object(json))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return do_dump(json, flags, 0, callback, data);
|
||||
}
|
||||
|
||||
@@ -18,10 +18,12 @@ void jsonp_error_init(json_error_t *error, const char *source)
|
||||
|
||||
void jsonp_error_set_source(json_error_t *error, const char *source)
|
||||
{
|
||||
size_t length;
|
||||
|
||||
if(!error || !source)
|
||||
return;
|
||||
|
||||
size_t length = strlen(source);
|
||||
length = strlen(source);
|
||||
if(length < JSON_ERROR_SOURCE_LENGTH)
|
||||
strcpy(error->source, source);
|
||||
else {
|
||||
|
||||
114
src/hashtable.c
114
src/hashtable.c
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <jansson_config.h> /* for JSON_INLINE */
|
||||
#include "jansson_private.h" /* for container_of() */
|
||||
#include "hashtable.h"
|
||||
@@ -16,6 +17,23 @@ typedef struct hashtable_bucket bucket_t;
|
||||
|
||||
#define list_to_pair(list_) container_of(list_, pair_t, list)
|
||||
|
||||
/* From http://www.cse.yorku.ca/~oz/hash.html */
|
||||
static size_t hash_str(const void *ptr)
|
||||
{
|
||||
const char *str = (const char *)ptr;
|
||||
|
||||
size_t hash = 5381;
|
||||
size_t c;
|
||||
|
||||
while((c = (size_t)*str))
|
||||
{
|
||||
hash = ((hash << 5) + hash) + c;
|
||||
str++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static JSON_INLINE void list_init(list_t *list)
|
||||
{
|
||||
list->next = list;
|
||||
@@ -62,7 +80,6 @@ static size_t primes[] = {
|
||||
12582917, 25165843, 50331653, 100663319, 201326611, 402653189,
|
||||
805306457, 1610612741
|
||||
};
|
||||
static const size_t num_primes = sizeof(primes) / sizeof(size_t);
|
||||
|
||||
static JSON_INLINE size_t num_buckets(hashtable_t *hashtable)
|
||||
{
|
||||
@@ -71,7 +88,7 @@ static JSON_INLINE size_t num_buckets(hashtable_t *hashtable)
|
||||
|
||||
|
||||
static pair_t *hashtable_find_pair(hashtable_t *hashtable, bucket_t *bucket,
|
||||
const void *key, size_t hash)
|
||||
const char *key, size_t hash)
|
||||
{
|
||||
list_t *list;
|
||||
pair_t *pair;
|
||||
@@ -83,7 +100,7 @@ static pair_t *hashtable_find_pair(hashtable_t *hashtable, bucket_t *bucket,
|
||||
while(1)
|
||||
{
|
||||
pair = list_to_pair(list);
|
||||
if(pair->hash == hash && hashtable->cmp_keys(pair->key, key))
|
||||
if(pair->hash == hash && strcmp(pair->key, key) == 0)
|
||||
return pair;
|
||||
|
||||
if(list == bucket->last)
|
||||
@@ -97,7 +114,7 @@ static pair_t *hashtable_find_pair(hashtable_t *hashtable, bucket_t *bucket,
|
||||
|
||||
/* returns 0 on success, -1 if key was not found */
|
||||
static int hashtable_do_del(hashtable_t *hashtable,
|
||||
const void *key, size_t hash)
|
||||
const char *key, size_t hash)
|
||||
{
|
||||
pair_t *pair;
|
||||
bucket_t *bucket;
|
||||
@@ -120,13 +137,9 @@ static int hashtable_do_del(hashtable_t *hashtable,
|
||||
bucket->last = pair->list.prev;
|
||||
|
||||
list_remove(&pair->list);
|
||||
json_decref(pair->value);
|
||||
|
||||
if(hashtable->free_key)
|
||||
hashtable->free_key(pair->key);
|
||||
if(hashtable->free_value)
|
||||
hashtable->free_value(pair->value);
|
||||
|
||||
free(pair);
|
||||
jsonp_free(pair);
|
||||
hashtable->size--;
|
||||
|
||||
return 0;
|
||||
@@ -141,10 +154,7 @@ static void hashtable_do_clear(hashtable_t *hashtable)
|
||||
{
|
||||
next = list->next;
|
||||
pair = list_to_pair(list);
|
||||
if(hashtable->free_key)
|
||||
hashtable->free_key(pair->key);
|
||||
if(hashtable->free_value)
|
||||
hashtable->free_value(pair->value);
|
||||
json_decref(pair->value);
|
||||
jsonp_free(pair);
|
||||
}
|
||||
}
|
||||
@@ -184,31 +194,7 @@ static int hashtable_do_rehash(hashtable_t *hashtable)
|
||||
}
|
||||
|
||||
|
||||
hashtable_t *hashtable_create(key_hash_fn hash_key, key_cmp_fn cmp_keys,
|
||||
free_fn free_key, free_fn free_value)
|
||||
{
|
||||
hashtable_t *hashtable = jsonp_malloc(sizeof(hashtable_t));
|
||||
if(!hashtable)
|
||||
return NULL;
|
||||
|
||||
if(hashtable_init(hashtable, hash_key, cmp_keys, free_key, free_value))
|
||||
{
|
||||
jsonp_free(hashtable);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return hashtable;
|
||||
}
|
||||
|
||||
void hashtable_destroy(hashtable_t *hashtable)
|
||||
{
|
||||
hashtable_close(hashtable);
|
||||
jsonp_free(hashtable);
|
||||
}
|
||||
|
||||
int hashtable_init(hashtable_t *hashtable,
|
||||
key_hash_fn hash_key, key_cmp_fn cmp_keys,
|
||||
free_fn free_key, free_fn free_value)
|
||||
int hashtable_init(hashtable_t *hashtable)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
@@ -220,11 +206,6 @@ int hashtable_init(hashtable_t *hashtable,
|
||||
|
||||
list_init(&hashtable->list);
|
||||
|
||||
hashtable->hash_key = hash_key;
|
||||
hashtable->cmp_keys = cmp_keys;
|
||||
hashtable->free_key = free_key;
|
||||
hashtable->free_value = free_value;
|
||||
|
||||
for(i = 0; i < num_buckets(hashtable); i++)
|
||||
{
|
||||
hashtable->buckets[i].first = hashtable->buckets[i].last =
|
||||
@@ -240,7 +221,9 @@ void hashtable_close(hashtable_t *hashtable)
|
||||
jsonp_free(hashtable->buckets);
|
||||
}
|
||||
|
||||
int hashtable_set(hashtable_t *hashtable, void *key, void *value)
|
||||
int hashtable_set(hashtable_t *hashtable,
|
||||
const char *key, size_t serial,
|
||||
json_t *value)
|
||||
{
|
||||
pair_t *pair;
|
||||
bucket_t *bucket;
|
||||
@@ -251,28 +234,29 @@ int hashtable_set(hashtable_t *hashtable, void *key, void *value)
|
||||
if(hashtable_do_rehash(hashtable))
|
||||
return -1;
|
||||
|
||||
hash = hashtable->hash_key(key);
|
||||
hash = hash_str(key);
|
||||
index = hash % num_buckets(hashtable);
|
||||
bucket = &hashtable->buckets[index];
|
||||
pair = hashtable_find_pair(hashtable, bucket, key, hash);
|
||||
|
||||
if(pair)
|
||||
{
|
||||
if(hashtable->free_key)
|
||||
hashtable->free_key(key);
|
||||
if(hashtable->free_value)
|
||||
hashtable->free_value(pair->value);
|
||||
json_decref(pair->value);
|
||||
pair->value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
pair = jsonp_malloc(sizeof(pair_t));
|
||||
/* offsetof(...) returns the size of pair_t without the last,
|
||||
flexible member. This way, the correct amount is
|
||||
allocated. */
|
||||
pair = jsonp_malloc(offsetof(pair_t, key) + strlen(key) + 1);
|
||||
if(!pair)
|
||||
return -1;
|
||||
|
||||
pair->key = key;
|
||||
pair->value = value;
|
||||
pair->hash = hash;
|
||||
pair->serial = serial;
|
||||
strcpy(pair->key, key);
|
||||
pair->value = value;
|
||||
list_init(&pair->list);
|
||||
|
||||
insert_to_bucket(hashtable, bucket, &pair->list);
|
||||
@@ -282,13 +266,13 @@ int hashtable_set(hashtable_t *hashtable, void *key, void *value)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *hashtable_get(hashtable_t *hashtable, const void *key)
|
||||
void *hashtable_get(hashtable_t *hashtable, const char *key)
|
||||
{
|
||||
pair_t *pair;
|
||||
size_t hash;
|
||||
bucket_t *bucket;
|
||||
|
||||
hash = hashtable->hash_key(key);
|
||||
hash = hash_str(key);
|
||||
bucket = &hashtable->buckets[hash % num_buckets(hashtable)];
|
||||
|
||||
pair = hashtable_find_pair(hashtable, bucket, key, hash);
|
||||
@@ -298,9 +282,9 @@ void *hashtable_get(hashtable_t *hashtable, const void *key)
|
||||
return pair->value;
|
||||
}
|
||||
|
||||
int hashtable_del(hashtable_t *hashtable, const void *key)
|
||||
int hashtable_del(hashtable_t *hashtable, const char *key)
|
||||
{
|
||||
size_t hash = hashtable->hash_key(key);
|
||||
size_t hash = hash_str(key);
|
||||
return hashtable_do_del(hashtable, key, hash);
|
||||
}
|
||||
|
||||
@@ -325,13 +309,13 @@ void *hashtable_iter(hashtable_t *hashtable)
|
||||
return hashtable_iter_next(hashtable, &hashtable->list);
|
||||
}
|
||||
|
||||
void *hashtable_iter_at(hashtable_t *hashtable, const void *key)
|
||||
void *hashtable_iter_at(hashtable_t *hashtable, const char *key)
|
||||
{
|
||||
pair_t *pair;
|
||||
size_t hash;
|
||||
bucket_t *bucket;
|
||||
|
||||
hash = hashtable->hash_key(key);
|
||||
hash = hash_str(key);
|
||||
bucket = &hashtable->buckets[hash % num_buckets(hashtable)];
|
||||
|
||||
pair = hashtable_find_pair(hashtable, bucket, key, hash);
|
||||
@@ -355,18 +339,22 @@ void *hashtable_iter_key(void *iter)
|
||||
return pair->key;
|
||||
}
|
||||
|
||||
size_t hashtable_iter_serial(void *iter)
|
||||
{
|
||||
pair_t *pair = list_to_pair((list_t *)iter);
|
||||
return pair->serial;
|
||||
}
|
||||
|
||||
void *hashtable_iter_value(void *iter)
|
||||
{
|
||||
pair_t *pair = list_to_pair((list_t *)iter);
|
||||
return pair->value;
|
||||
}
|
||||
|
||||
void hashtable_iter_set(hashtable_t *hashtable, void *iter, void *value)
|
||||
void hashtable_iter_set(void *iter, json_t *value)
|
||||
{
|
||||
pair_t *pair = list_to_pair((list_t *)iter);
|
||||
|
||||
if(hashtable->free_value)
|
||||
hashtable->free_value(pair->value);
|
||||
|
||||
json_decref(pair->value);
|
||||
pair->value = value;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,15 @@ struct hashtable_list {
|
||||
struct hashtable_list *next;
|
||||
};
|
||||
|
||||
/* "pair" may be a bit confusing a name, but think of it as a
|
||||
key-value pair. In this case, it just encodes some extra data,
|
||||
too */
|
||||
struct hashtable_pair {
|
||||
void *key;
|
||||
void *value;
|
||||
size_t hash;
|
||||
struct hashtable_list list;
|
||||
json_t *value;
|
||||
size_t serial;
|
||||
char key[1];
|
||||
};
|
||||
|
||||
struct hashtable_bucket {
|
||||
@@ -34,56 +38,23 @@ typedef struct hashtable {
|
||||
struct hashtable_bucket *buckets;
|
||||
size_t num_buckets; /* index to primes[] */
|
||||
struct hashtable_list list;
|
||||
|
||||
key_hash_fn hash_key;
|
||||
key_cmp_fn cmp_keys; /* returns non-zero for equal keys */
|
||||
free_fn free_key;
|
||||
free_fn free_value;
|
||||
} hashtable_t;
|
||||
|
||||
/**
|
||||
* hashtable_create - Create a hashtable object
|
||||
*
|
||||
* @hash_key: The key hashing function
|
||||
* @cmp_keys: The key compare function. Returns non-zero for equal and
|
||||
* zero for unequal unequal keys
|
||||
* @free_key: If non-NULL, called for a key that is no longer referenced.
|
||||
* @free_value: If non-NULL, called for a value that is no longer referenced.
|
||||
*
|
||||
* Returns a new hashtable object that should be freed with
|
||||
* hashtable_destroy when it's no longer used, or NULL on failure (out
|
||||
* of memory).
|
||||
*/
|
||||
hashtable_t *hashtable_create(key_hash_fn hash_key, key_cmp_fn cmp_keys,
|
||||
free_fn free_key, free_fn free_value);
|
||||
|
||||
/**
|
||||
* hashtable_destroy - Destroy a hashtable object
|
||||
*
|
||||
* @hashtable: The hashtable
|
||||
*
|
||||
* Destroys a hashtable created with hashtable_create().
|
||||
*/
|
||||
void hashtable_destroy(hashtable_t *hashtable);
|
||||
#define hashtable_key_to_iter(key_) \
|
||||
(&(container_of(key_, struct hashtable_pair, key)->list))
|
||||
|
||||
/**
|
||||
* hashtable_init - Initialize a hashtable object
|
||||
*
|
||||
* @hashtable: The (statically allocated) hashtable object
|
||||
* @hash_key: The key hashing function
|
||||
* @cmp_keys: The key compare function. Returns non-zero for equal and
|
||||
* zero for unequal unequal keys
|
||||
* @free_key: If non-NULL, called for a key that is no longer referenced.
|
||||
* @free_value: If non-NULL, called for a value that is no longer referenced.
|
||||
*
|
||||
* Initializes a statically allocated hashtable object. The object
|
||||
* should be cleared with hashtable_close when it's no longer used.
|
||||
*
|
||||
* Returns 0 on success, -1 on error (out of memory).
|
||||
*/
|
||||
int hashtable_init(hashtable_t *hashtable,
|
||||
key_hash_fn hash_key, key_cmp_fn cmp_keys,
|
||||
free_fn free_key, free_fn free_value);
|
||||
int hashtable_init(hashtable_t *hashtable);
|
||||
|
||||
/**
|
||||
* hashtable_close - Release all resources used by a hashtable object
|
||||
@@ -99,20 +70,19 @@ void hashtable_close(hashtable_t *hashtable);
|
||||
*
|
||||
* @hashtable: The hashtable object
|
||||
* @key: The key
|
||||
* @serial: For addition order of keys
|
||||
* @value: The value
|
||||
*
|
||||
* If a value with the given key already exists, its value is replaced
|
||||
* with the new value.
|
||||
*
|
||||
* Key and value are "stealed" in the sense that hashtable frees them
|
||||
* automatically when they are no longer used. The freeing is
|
||||
* accomplished by calling free_key and free_value functions that were
|
||||
* supplied to hashtable_new. In case one or both of the free
|
||||
* functions is NULL, the corresponding item is not "stealed".
|
||||
* with the new value. Value is "stealed" in the sense that hashtable
|
||||
* doesn't increment its refcount but decreases the refcount when the
|
||||
* value is no longer needed.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure (out of memory).
|
||||
*/
|
||||
int hashtable_set(hashtable_t *hashtable, void *key, void *value);
|
||||
int hashtable_set(hashtable_t *hashtable,
|
||||
const char *key, size_t serial,
|
||||
json_t *value);
|
||||
|
||||
/**
|
||||
* hashtable_get - Get a value associated with a key
|
||||
@@ -122,7 +92,7 @@ int hashtable_set(hashtable_t *hashtable, void *key, void *value);
|
||||
*
|
||||
* Returns value if it is found, or NULL otherwise.
|
||||
*/
|
||||
void *hashtable_get(hashtable_t *hashtable, const void *key);
|
||||
void *hashtable_get(hashtable_t *hashtable, const char *key);
|
||||
|
||||
/**
|
||||
* hashtable_del - Remove a value from the hashtable
|
||||
@@ -132,7 +102,7 @@ void *hashtable_get(hashtable_t *hashtable, const void *key);
|
||||
*
|
||||
* Returns 0 on success, or -1 if the key was not found.
|
||||
*/
|
||||
int hashtable_del(hashtable_t *hashtable, const void *key);
|
||||
int hashtable_del(hashtable_t *hashtable, const char *key);
|
||||
|
||||
/**
|
||||
* hashtable_clear - Clear hashtable
|
||||
@@ -169,7 +139,7 @@ void *hashtable_iter(hashtable_t *hashtable);
|
||||
* Like hashtable_iter() but returns an iterator pointing to a
|
||||
* specific key.
|
||||
*/
|
||||
void *hashtable_iter_at(hashtable_t *hashtable, const void *key);
|
||||
void *hashtable_iter_at(hashtable_t *hashtable, const char *key);
|
||||
|
||||
/**
|
||||
* hashtable_iter_next - Advance an iterator
|
||||
@@ -189,6 +159,13 @@ void *hashtable_iter_next(hashtable_t *hashtable, void *iter);
|
||||
*/
|
||||
void *hashtable_iter_key(void *iter);
|
||||
|
||||
/**
|
||||
* hashtable_iter_serial - Retrieve the serial number pointed to by an iterator
|
||||
*
|
||||
* @iter: The iterator
|
||||
*/
|
||||
size_t hashtable_iter_serial(void *iter);
|
||||
|
||||
/**
|
||||
* hashtable_iter_value - Retrieve the value pointed by an iterator
|
||||
*
|
||||
@@ -202,6 +179,6 @@ void *hashtable_iter_value(void *iter);
|
||||
* @iter: The iterator
|
||||
* @value: The value to set
|
||||
*/
|
||||
void hashtable_iter_set(hashtable_t *hashtable, void *iter, void *value);
|
||||
void hashtable_iter_set(void *iter, json_t *value);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,17 +21,17 @@ extern "C" {
|
||||
/* version */
|
||||
|
||||
#define JANSSON_MAJOR_VERSION 2
|
||||
#define JANSSON_MINOR_VERSION 0
|
||||
#define JANSSON_MINOR_VERSION 3
|
||||
#define JANSSON_MICRO_VERSION 0
|
||||
|
||||
/* Micro version is omitted if it's 0 */
|
||||
#define JANSSON_VERSION "2.0"
|
||||
#define JANSSON_VERSION "2.3"
|
||||
|
||||
/* Version as a 3-byte hex number, e.g. 0x010201 == 1.2.1. Use this
|
||||
for numeric comparisons, e.g. #if JANSSON_VERSION_HEX >= ... */
|
||||
#define JANSSON_VERSION_HEX ((JANSSON_MAJOR_VERSION << 16) | \
|
||||
(JANSSON_MINOR_VERSION << 8) | \
|
||||
(JANSSON_MICRO_VERSION << 0)))
|
||||
(JANSSON_MICRO_VERSION << 0))
|
||||
|
||||
|
||||
/* types */
|
||||
@@ -53,7 +53,11 @@ typedef struct {
|
||||
} json_t;
|
||||
|
||||
#if JSON_INTEGER_IS_LONG_LONG
|
||||
#ifdef _WIN32
|
||||
#define JSON_INTEGER_FORMAT "I64d"
|
||||
#else
|
||||
#define JSON_INTEGER_FORMAT "lld"
|
||||
#endif
|
||||
typedef long long json_int_t;
|
||||
#else
|
||||
#define JSON_INTEGER_FORMAT "ld"
|
||||
@@ -126,13 +130,21 @@ int json_object_set_new_nocheck(json_t *object, const char *key, json_t *value);
|
||||
int json_object_del(json_t *object, const char *key);
|
||||
int json_object_clear(json_t *object);
|
||||
int json_object_update(json_t *object, json_t *other);
|
||||
int json_object_update_existing(json_t *object, json_t *other);
|
||||
int json_object_update_missing(json_t *object, json_t *other);
|
||||
void *json_object_iter(json_t *object);
|
||||
void *json_object_iter_at(json_t *object, const char *key);
|
||||
void *json_object_key_to_iter(const char *key);
|
||||
void *json_object_iter_next(json_t *object, void *iter);
|
||||
const char *json_object_iter_key(void *iter);
|
||||
json_t *json_object_iter_value(void *iter);
|
||||
int json_object_iter_set_new(json_t *object, void *iter, json_t *value);
|
||||
|
||||
#define json_object_foreach(object, key, value) \
|
||||
for(key = json_object_iter_key(json_object_iter(object)); \
|
||||
key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
|
||||
key = json_object_iter_key(json_object_iter_next(object, json_object_key_to_iter(key))))
|
||||
|
||||
static JSON_INLINE
|
||||
int json_object_set(json_t *object, const char *key, json_t *value)
|
||||
{
|
||||
@@ -214,22 +226,33 @@ json_t *json_copy(json_t *value);
|
||||
json_t *json_deep_copy(json_t *value);
|
||||
|
||||
|
||||
/* loading, printing */
|
||||
/* decoding */
|
||||
|
||||
#define JSON_REJECT_DUPLICATES 0x1
|
||||
#define JSON_DISABLE_EOF_CHECK 0x2
|
||||
#define JSON_DECODE_ANY 0x4
|
||||
|
||||
json_t *json_loads(const char *input, size_t flags, json_error_t *error);
|
||||
json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error);
|
||||
json_t *json_loadf(FILE *input, size_t flags, json_error_t *error);
|
||||
json_t *json_load_file(const char *path, size_t flags, json_error_t *error);
|
||||
|
||||
|
||||
/* encoding */
|
||||
|
||||
#define JSON_INDENT(n) (n & 0x1F)
|
||||
#define JSON_COMPACT 0x20
|
||||
#define JSON_ENSURE_ASCII 0x40
|
||||
#define JSON_SORT_KEYS 0x80
|
||||
#define JSON_PRESERVE_ORDER 0x100
|
||||
#define JSON_ENCODE_ANY 0x200
|
||||
|
||||
typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data);
|
||||
|
||||
char *json_dumps(const json_t *json, size_t flags);
|
||||
int json_dumpf(const json_t *json, FILE *output, size_t flags);
|
||||
int json_dump_file(const json_t *json, const char *path, size_t flags);
|
||||
|
||||
int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags);
|
||||
|
||||
/* custom memory allocation */
|
||||
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
|
||||
#define JSON_INTEGER_IS_LONG_LONG @json_have_long_long@
|
||||
|
||||
/* If locale.h and localeconv() are available, define to 1,
|
||||
otherwise to 0. */
|
||||
#define JSON_HAVE_LOCALECONV @json_have_localeconv@
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
|
||||
#define JSON_INTEGER_IS_LONG_LONG 1
|
||||
|
||||
/* If locale.h and localeconv() are available, define to 1,
|
||||
otherwise to 0. */
|
||||
#define JSON_HAVE_LOCALECONV 1
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
#define JANSSON_PRIVATE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include "jansson.h"
|
||||
#include "hashtable.h"
|
||||
#include "strbuffer.h"
|
||||
|
||||
#define container_of(ptr_, type_, member_) \
|
||||
((type_ *)((char *)ptr_ - offsetof(type_, member_)))
|
||||
@@ -21,6 +21,16 @@
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
/* va_copy is a C99 feature. In C89 implementations, it's sometimes
|
||||
available as __va_copy. If not, memcpy() should do the trick. */
|
||||
#ifndef va_copy
|
||||
#ifdef __va_copy
|
||||
#define va_copy __va_copy
|
||||
#else
|
||||
#define va_copy(a, b) memcpy(&(a), &(b), sizeof(va_list))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
json_t json;
|
||||
hashtable_t hashtable;
|
||||
@@ -57,16 +67,6 @@ typedef struct {
|
||||
#define json_to_real(json_) container_of(json_, json_real_t, json)
|
||||
#define json_to_integer(json_) container_of(json_, json_integer_t, json)
|
||||
|
||||
size_t jsonp_hash_key(const void *ptr);
|
||||
int jsonp_key_equal(const void *ptr1, const void *ptr2);
|
||||
|
||||
typedef struct {
|
||||
size_t serial;
|
||||
char key[1];
|
||||
} object_key_t;
|
||||
|
||||
const object_key_t *jsonp_object_iter_fullkey(void *iter);
|
||||
|
||||
void jsonp_error_init(json_error_t *error, const char *source);
|
||||
void jsonp_error_set_source(json_error_t *error, const char *source);
|
||||
void jsonp_error_set(json_error_t *error, int line, int column,
|
||||
@@ -74,6 +74,10 @@ void jsonp_error_set(json_error_t *error, int line, int column,
|
||||
void jsonp_error_vset(json_error_t *error, int line, int column,
|
||||
size_t position, const char *msg, va_list ap);
|
||||
|
||||
/* Locale independent string<->double conversions */
|
||||
int jsonp_strtod(strbuffer_t *strbuffer, double *out);
|
||||
int jsonp_dtostr(char *buffer, size_t size, double value);
|
||||
|
||||
/* Wrappers for custom memory functions */
|
||||
void* jsonp_malloc(size_t size);
|
||||
void jsonp_free(void *ptr);
|
||||
|
||||
196
src/load.c
196
src/load.c
@@ -6,13 +6,11 @@
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <jansson.h>
|
||||
@@ -33,6 +31,14 @@
|
||||
#define TOKEN_FALSE 260
|
||||
#define TOKEN_NULL 261
|
||||
|
||||
/* Locale independent versions of isxxx() functions */
|
||||
#define l_isupper(c) ('A' <= c && c <= 'Z')
|
||||
#define l_islower(c) ('a' <= c && c <= 'z')
|
||||
#define l_isalpha(c) (l_isupper(c) || l_islower(c))
|
||||
#define l_isdigit(c) ('0' <= c && c <= '9')
|
||||
#define l_isxdigit(c) \
|
||||
(l_isdigit(c) || 'A' <= c || c <= 'F' || 'a' <= c || c <= 'f')
|
||||
|
||||
/* Read one byte from stream, convert to unsigned char, then int, and
|
||||
return. return EOF on end of file. This corresponds to the
|
||||
behaviour of fgetc(). */
|
||||
@@ -70,6 +76,7 @@ static void error_set(json_error_t *error, const lex_t *lex,
|
||||
{
|
||||
va_list ap;
|
||||
char msg_text[JSON_ERROR_TEXT_LENGTH];
|
||||
char msg_with_context[JSON_ERROR_TEXT_LENGTH];
|
||||
|
||||
int line = -1, col = -1;
|
||||
size_t pos = 0;
|
||||
@@ -85,7 +92,6 @@ static void error_set(json_error_t *error, const lex_t *lex,
|
||||
if(lex)
|
||||
{
|
||||
const char *saved_text = strbuffer_value(&lex->saved_text);
|
||||
char msg_with_context[JSON_ERROR_TEXT_LENGTH];
|
||||
|
||||
line = lex->stream.line;
|
||||
col = lex->stream.column;
|
||||
@@ -269,11 +275,11 @@ static int32_t decode_unicode_escape(const char *str)
|
||||
for(i = 1; i <= 4; i++) {
|
||||
char c = str[i];
|
||||
value <<= 4;
|
||||
if(isdigit(c))
|
||||
if(l_isdigit(c))
|
||||
value += c - '0';
|
||||
else if(islower(c))
|
||||
else if(l_islower(c))
|
||||
value += c - 'a' + 10;
|
||||
else if(isupper(c))
|
||||
else if(l_isupper(c))
|
||||
value += c - 'A' + 10;
|
||||
else
|
||||
assert(0);
|
||||
@@ -318,7 +324,7 @@ static void lex_scan_string(lex_t *lex, json_error_t *error)
|
||||
if(c == 'u') {
|
||||
c = lex_get_save(lex, error);
|
||||
for(i = 0; i < 4; i++) {
|
||||
if(!isxdigit(c)) {
|
||||
if(!l_isxdigit(c)) {
|
||||
error_set(error, lex, "invalid escape");
|
||||
goto out;
|
||||
}
|
||||
@@ -456,14 +462,14 @@ static int lex_scan_number(lex_t *lex, int c, json_error_t *error)
|
||||
|
||||
if(c == '0') {
|
||||
c = lex_get_save(lex, error);
|
||||
if(isdigit(c)) {
|
||||
if(l_isdigit(c)) {
|
||||
lex_unget_unsave(lex, c);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else if(isdigit(c)) {
|
||||
else if(l_isdigit(c)) {
|
||||
c = lex_get_save(lex, error);
|
||||
while(isdigit(c))
|
||||
while(l_isdigit(c))
|
||||
c = lex_get_save(lex, error);
|
||||
}
|
||||
else {
|
||||
@@ -497,14 +503,14 @@ static int lex_scan_number(lex_t *lex, int c, json_error_t *error)
|
||||
|
||||
if(c == '.') {
|
||||
c = lex_get(lex, error);
|
||||
if(!isdigit(c)) {
|
||||
if(!l_isdigit(c)) {
|
||||
lex_unget(lex, c);
|
||||
goto out;
|
||||
}
|
||||
lex_save(lex, c);
|
||||
|
||||
c = lex_get_save(lex, error);
|
||||
while(isdigit(c))
|
||||
while(l_isdigit(c))
|
||||
c = lex_get_save(lex, error);
|
||||
}
|
||||
|
||||
@@ -513,23 +519,19 @@ static int lex_scan_number(lex_t *lex, int c, json_error_t *error)
|
||||
if(c == '+' || c == '-')
|
||||
c = lex_get_save(lex, error);
|
||||
|
||||
if(!isdigit(c)) {
|
||||
if(!l_isdigit(c)) {
|
||||
lex_unget_unsave(lex, c);
|
||||
goto out;
|
||||
}
|
||||
|
||||
c = lex_get_save(lex, error);
|
||||
while(isdigit(c))
|
||||
while(l_isdigit(c))
|
||||
c = lex_get_save(lex, error);
|
||||
}
|
||||
|
||||
lex_unget_unsave(lex, c);
|
||||
|
||||
saved_text = strbuffer_value(&lex->saved_text);
|
||||
value = strtod(saved_text, &end);
|
||||
assert(end == saved_text + lex->saved_text.length);
|
||||
|
||||
if(errno == ERANGE && value != 0) {
|
||||
if(jsonp_strtod(&lex->saved_text, &value)) {
|
||||
error_set(error, lex, "real number overflow");
|
||||
goto out;
|
||||
}
|
||||
@@ -575,17 +577,17 @@ static int lex_scan(lex_t *lex, json_error_t *error)
|
||||
else if(c == '"')
|
||||
lex_scan_string(lex, error);
|
||||
|
||||
else if(isdigit(c) || c == '-') {
|
||||
else if(l_isdigit(c) || c == '-') {
|
||||
if(lex_scan_number(lex, c, error))
|
||||
goto out;
|
||||
}
|
||||
|
||||
else if(isupper(c) || islower(c)) {
|
||||
else if(l_isalpha(c)) {
|
||||
/* eat up the whole identifier for clearer error messages */
|
||||
const char *saved_text;
|
||||
|
||||
c = lex_get_save(lex, error);
|
||||
while(isupper(c) || islower(c))
|
||||
while(l_isalpha(c))
|
||||
c = lex_get_save(lex, error);
|
||||
lex_unget_unsave(lex, c);
|
||||
|
||||
@@ -643,9 +645,9 @@ static void lex_close(lex_t *lex)
|
||||
|
||||
/*** parser ***/
|
||||
|
||||
static json_t *parse_value(lex_t *lex, json_error_t *error);
|
||||
static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error);
|
||||
|
||||
static json_t *parse_object(lex_t *lex, json_error_t *error)
|
||||
static json_t *parse_object(lex_t *lex, size_t flags, json_error_t *error)
|
||||
{
|
||||
json_t *object = json_object();
|
||||
if(!object)
|
||||
@@ -668,6 +670,14 @@ static json_t *parse_object(lex_t *lex, json_error_t *error)
|
||||
if(!key)
|
||||
return NULL;
|
||||
|
||||
if(flags & JSON_REJECT_DUPLICATES) {
|
||||
if(json_object_get(object, key)) {
|
||||
jsonp_free(key);
|
||||
error_set(error, lex, "duplicate object key");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
lex_scan(lex, error);
|
||||
if(lex->token != ':') {
|
||||
jsonp_free(key);
|
||||
@@ -676,7 +686,7 @@ static json_t *parse_object(lex_t *lex, json_error_t *error)
|
||||
}
|
||||
|
||||
lex_scan(lex, error);
|
||||
value = parse_value(lex, error);
|
||||
value = parse_value(lex, flags, error);
|
||||
if(!value) {
|
||||
jsonp_free(key);
|
||||
goto error;
|
||||
@@ -710,7 +720,7 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static json_t *parse_array(lex_t *lex, json_error_t *error)
|
||||
static json_t *parse_array(lex_t *lex, size_t flags, json_error_t *error)
|
||||
{
|
||||
json_t *array = json_array();
|
||||
if(!array)
|
||||
@@ -721,7 +731,7 @@ static json_t *parse_array(lex_t *lex, json_error_t *error)
|
||||
return array;
|
||||
|
||||
while(lex->token) {
|
||||
json_t *elem = parse_value(lex, error);
|
||||
json_t *elem = parse_value(lex, flags, error);
|
||||
if(!elem)
|
||||
goto error;
|
||||
|
||||
@@ -750,7 +760,7 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static json_t *parse_value(lex_t *lex, json_error_t *error)
|
||||
static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error)
|
||||
{
|
||||
json_t *json;
|
||||
|
||||
@@ -783,11 +793,11 @@ static json_t *parse_value(lex_t *lex, json_error_t *error)
|
||||
break;
|
||||
|
||||
case '{':
|
||||
json = parse_object(lex, error);
|
||||
json = parse_object(lex, flags, error);
|
||||
break;
|
||||
|
||||
case '[':
|
||||
json = parse_array(lex, error);
|
||||
json = parse_array(lex, flags, error);
|
||||
break;
|
||||
|
||||
case TOKEN_INVALID:
|
||||
@@ -805,15 +815,37 @@ static json_t *parse_value(lex_t *lex, json_error_t *error)
|
||||
return json;
|
||||
}
|
||||
|
||||
static json_t *parse_json(lex_t *lex, json_error_t *error)
|
||||
static json_t *parse_json(lex_t *lex, size_t flags, json_error_t *error)
|
||||
{
|
||||
json_t *result;
|
||||
|
||||
lex_scan(lex, error);
|
||||
if(lex->token != '[' && lex->token != '{') {
|
||||
error_set(error, lex, "'[' or '{' expected");
|
||||
return NULL;
|
||||
if(!(flags & JSON_DECODE_ANY)) {
|
||||
if(lex->token != '[' && lex->token != '{') {
|
||||
error_set(error, lex, "'[' or '{' expected");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return parse_value(lex, error);
|
||||
result = parse_value(lex, flags, error);
|
||||
if(!result)
|
||||
return NULL;
|
||||
|
||||
if(!(flags & JSON_DISABLE_EOF_CHECK)) {
|
||||
lex_scan(lex, error);
|
||||
if(lex->token != TOKEN_EOF) {
|
||||
error_set(error, lex, "end of file expected");
|
||||
json_decref(result);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if(error) {
|
||||
/* Save the position even though there was no error */
|
||||
error->position = lex->stream.position;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
@@ -840,27 +872,68 @@ json_t *json_loads(const char *string, size_t flags, json_error_t *error)
|
||||
{
|
||||
lex_t lex;
|
||||
json_t *result;
|
||||
string_data_t stream_data = {string, 0};
|
||||
string_data_t stream_data;
|
||||
|
||||
(void)flags; /* unused */
|
||||
jsonp_error_init(error, "<string>");
|
||||
|
||||
if (string == NULL) {
|
||||
error_set(error, NULL, "wrong arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stream_data.data = string;
|
||||
stream_data.pos = 0;
|
||||
|
||||
if(lex_init(&lex, string_get, (void *)&stream_data))
|
||||
return NULL;
|
||||
|
||||
jsonp_error_init(error, "<string>");
|
||||
result = parse_json(&lex, flags, error);
|
||||
|
||||
result = parse_json(&lex, error);
|
||||
if(!result)
|
||||
goto out;
|
||||
lex_close(&lex);
|
||||
return result;
|
||||
}
|
||||
|
||||
lex_scan(&lex, error);
|
||||
if(lex.token != TOKEN_EOF) {
|
||||
error_set(error, &lex, "end of file expected");
|
||||
json_decref(result);
|
||||
result = NULL;
|
||||
typedef struct
|
||||
{
|
||||
const char *data;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
} buffer_data_t;
|
||||
|
||||
static int buffer_get(void *data)
|
||||
{
|
||||
char c;
|
||||
buffer_data_t *stream = data;
|
||||
if(stream->pos >= stream->len)
|
||||
return EOF;
|
||||
|
||||
c = stream->data[stream->pos];
|
||||
stream->pos++;
|
||||
return (unsigned char)c;
|
||||
}
|
||||
|
||||
json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error)
|
||||
{
|
||||
lex_t lex;
|
||||
json_t *result;
|
||||
buffer_data_t stream_data;
|
||||
|
||||
jsonp_error_init(error, "<buffer>");
|
||||
|
||||
if (buffer == NULL) {
|
||||
error_set(error, NULL, "wrong arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
stream_data.data = buffer;
|
||||
stream_data.pos = 0;
|
||||
stream_data.len = buflen;
|
||||
|
||||
if(lex_init(&lex, buffer_get, (void *)&stream_data))
|
||||
return NULL;
|
||||
|
||||
result = parse_json(&lex, flags, error);
|
||||
|
||||
lex_close(&lex);
|
||||
return result;
|
||||
}
|
||||
@@ -870,10 +943,6 @@ json_t *json_loadf(FILE *input, size_t flags, json_error_t *error)
|
||||
lex_t lex;
|
||||
const char *source;
|
||||
json_t *result;
|
||||
(void)flags; /* unused */
|
||||
|
||||
if(lex_init(&lex, (get_func)fgetc, input))
|
||||
return NULL;
|
||||
|
||||
if(input == stdin)
|
||||
source = "<stdin>";
|
||||
@@ -882,18 +951,16 @@ json_t *json_loadf(FILE *input, size_t flags, json_error_t *error)
|
||||
|
||||
jsonp_error_init(error, source);
|
||||
|
||||
result = parse_json(&lex, error);
|
||||
if(!result)
|
||||
goto out;
|
||||
|
||||
lex_scan(&lex, error);
|
||||
if(lex.token != TOKEN_EOF) {
|
||||
error_set(error, &lex, "end of file expected");
|
||||
json_decref(result);
|
||||
result = NULL;
|
||||
if (input == NULL) {
|
||||
error_set(error, NULL, "wrong arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
if(lex_init(&lex, (get_func)fgetc, input))
|
||||
return NULL;
|
||||
|
||||
result = parse_json(&lex, flags, error);
|
||||
|
||||
lex_close(&lex);
|
||||
return result;
|
||||
}
|
||||
@@ -905,7 +972,12 @@ json_t *json_load_file(const char *path, size_t flags, json_error_t *error)
|
||||
|
||||
jsonp_error_init(error, path);
|
||||
|
||||
fp = fopen(path, "r");
|
||||
if (path == NULL) {
|
||||
error_set(error, NULL, "wrong arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fp = fopen(path, "rb");
|
||||
if(!fp)
|
||||
{
|
||||
error_set(error, NULL, "unable to open %s: %s",
|
||||
|
||||
@@ -233,12 +233,12 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
|
||||
*/
|
||||
hashtable_t key_set;
|
||||
|
||||
if(hashtable_init(&key_set, jsonp_hash_key, jsonp_key_equal, NULL, NULL)) {
|
||||
if(hashtable_init(&key_set)) {
|
||||
set_error(s, "<internal>", "Out of memory");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!json_is_object(root)) {
|
||||
if(root && !json_is_object(root)) {
|
||||
set_error(s, "<validation>", "Expected object, got %s",
|
||||
type_name(root));
|
||||
goto out;
|
||||
@@ -248,6 +248,7 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
|
||||
while(s->token != '}') {
|
||||
const char *key;
|
||||
json_t *value;
|
||||
int opt = 0;
|
||||
|
||||
if(strict != 0) {
|
||||
set_error(s, "<format>", "Expected '}' after '%c', got '%c'",
|
||||
@@ -279,23 +280,34 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
|
||||
|
||||
next_token(s);
|
||||
|
||||
value = json_object_get(root, key);
|
||||
if(!value) {
|
||||
set_error(s, "<validation>", "Object item not found: %s", key);
|
||||
goto out;
|
||||
if(s->token == '?') {
|
||||
opt = 1;
|
||||
next_token(s);
|
||||
}
|
||||
|
||||
if(!root) {
|
||||
/* skipping */
|
||||
value = NULL;
|
||||
}
|
||||
else {
|
||||
value = json_object_get(root, key);
|
||||
if(!value && !opt) {
|
||||
set_error(s, "<validation>", "Object item not found: %s", key);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if(unpack(s, value, ap))
|
||||
goto out;
|
||||
|
||||
hashtable_set(&key_set, (void *)key, NULL);
|
||||
hashtable_set(&key_set, key, 0, json_null());
|
||||
next_token(s);
|
||||
}
|
||||
|
||||
if(strict == 0 && (s->flags & JSON_STRICT))
|
||||
strict = 1;
|
||||
|
||||
if(strict == 1 && key_set.size != json_object_size(root)) {
|
||||
if(root && strict == 1 && key_set.size != json_object_size(root)) {
|
||||
long diff = (long)json_object_size(root) - (long)key_set.size;
|
||||
set_error(s, "<validation>", "%li object item(s) left unpacked", diff);
|
||||
goto out;
|
||||
@@ -313,7 +325,7 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
||||
size_t i = 0;
|
||||
int strict = 0;
|
||||
|
||||
if(!json_is_array(root)) {
|
||||
if(root && !json_is_array(root)) {
|
||||
set_error(s, "<validation>", "Expected array, got %s", type_name(root));
|
||||
return -1;
|
||||
}
|
||||
@@ -346,11 +358,17 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
||||
return -1;
|
||||
}
|
||||
|
||||
value = json_array_get(root, i);
|
||||
if(!value) {
|
||||
set_error(s, "<validation>", "Array index %lu out of range",
|
||||
(unsigned long)i);
|
||||
return -1;
|
||||
if(!root) {
|
||||
/* skipping */
|
||||
value = NULL;
|
||||
}
|
||||
else {
|
||||
value = json_array_get(root, i);
|
||||
if(!value) {
|
||||
set_error(s, "<validation>", "Array index %lu out of range",
|
||||
(unsigned long)i);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(unpack(s, value, ap))
|
||||
@@ -363,7 +381,7 @@ static int unpack_array(scanner_t *s, json_t *root, va_list *ap)
|
||||
if(strict == 0 && (s->flags & JSON_STRICT))
|
||||
strict = 1;
|
||||
|
||||
if(strict == 1 && i != json_array_size(root)) {
|
||||
if(root && strict == 1 && i != json_array_size(root)) {
|
||||
long diff = (long)json_array_size(root) - (long)i;
|
||||
set_error(s, "<validation>", "%li array item(s) left unpacked", diff);
|
||||
return -1;
|
||||
@@ -383,99 +401,118 @@ static int unpack(scanner_t *s, json_t *root, va_list *ap)
|
||||
return unpack_array(s, root, ap);
|
||||
|
||||
case 's':
|
||||
if(!json_is_string(root)) {
|
||||
if(root && !json_is_string(root)) {
|
||||
set_error(s, "<validation>", "Expected string, got %s",
|
||||
type_name(root));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY)) {
|
||||
const char **str;
|
||||
const char **target;
|
||||
|
||||
str = va_arg(*ap, const char **);
|
||||
if(!str) {
|
||||
target = va_arg(*ap, const char **);
|
||||
if(!target) {
|
||||
set_error(s, "<args>", "NULL string argument");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*str = json_string_value(root);
|
||||
if(root)
|
||||
*target = json_string_value(root);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case 'i':
|
||||
if(!json_is_integer(root)) {
|
||||
if(root && !json_is_integer(root)) {
|
||||
set_error(s, "<validation>", "Expected integer, got %s",
|
||||
type_name(root));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY))
|
||||
*va_arg(*ap, int*) = json_integer_value(root);
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY)) {
|
||||
int *target = va_arg(*ap, int*);
|
||||
if(root)
|
||||
*target = json_integer_value(root);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case 'I':
|
||||
if(!json_is_integer(root)) {
|
||||
if(root && !json_is_integer(root)) {
|
||||
set_error(s, "<validation>", "Expected integer, got %s",
|
||||
type_name(root));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY))
|
||||
*va_arg(*ap, json_int_t*) = json_integer_value(root);
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY)) {
|
||||
json_int_t *target = va_arg(*ap, json_int_t*);
|
||||
if(root)
|
||||
*target = json_integer_value(root);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case 'b':
|
||||
if(!json_is_boolean(root)) {
|
||||
if(root && !json_is_boolean(root)) {
|
||||
set_error(s, "<validation>", "Expected true or false, got %s",
|
||||
type_name(root));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY))
|
||||
*va_arg(*ap, int*) = json_is_true(root);
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY)) {
|
||||
int *target = va_arg(*ap, int*);
|
||||
if(root)
|
||||
*target = json_is_true(root);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case 'f':
|
||||
if(!json_is_real(root)) {
|
||||
if(root && !json_is_real(root)) {
|
||||
set_error(s, "<validation>", "Expected real, got %s",
|
||||
type_name(root));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY))
|
||||
*va_arg(*ap, double*) = json_real_value(root);
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY)) {
|
||||
double *target = va_arg(*ap, double*);
|
||||
if(root)
|
||||
*target = json_real_value(root);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case 'F':
|
||||
if(!json_is_number(root)) {
|
||||
if(root && !json_is_number(root)) {
|
||||
set_error(s, "<validation>", "Expected real or integer, got %s",
|
||||
type_name(root));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY))
|
||||
*va_arg(*ap, double*) = json_number_value(root);
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY)) {
|
||||
double *target = va_arg(*ap, double*);
|
||||
if(root)
|
||||
*target = json_number_value(root);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case 'O':
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY))
|
||||
if(root && !(s->flags & JSON_VALIDATE_ONLY))
|
||||
json_incref(root);
|
||||
/* Fall through */
|
||||
|
||||
case 'o':
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY))
|
||||
*va_arg(*ap, json_t**) = root;
|
||||
if(!(s->flags & JSON_VALIDATE_ONLY)) {
|
||||
json_t **target = va_arg(*ap, json_t**);
|
||||
if(root)
|
||||
*target = root;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case 'n':
|
||||
/* Never assign, just validate */
|
||||
if(!json_is_null(root)) {
|
||||
if(root && !json_is_null(root)) {
|
||||
set_error(s, "<validation>", "Expected null, got %s",
|
||||
type_name(root));
|
||||
return -1;
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
|
||||
#define STRBUFFER_MIN_SIZE 16
|
||||
#define STRBUFFER_FACTOR 2
|
||||
#define STRBUFFER_SIZE_MAX ((size_t)-1)
|
||||
|
||||
int strbuffer_init(strbuffer_t *strbuff)
|
||||
{
|
||||
strbuff->size = STRBUFFER_MIN_SIZE;
|
||||
strbuff->length = 0;
|
||||
|
||||
strbuff->value = malloc(strbuff->size);
|
||||
strbuff->value = jsonp_malloc(strbuff->size);
|
||||
if(!strbuff->value)
|
||||
return -1;
|
||||
|
||||
@@ -30,7 +31,7 @@ int strbuffer_init(strbuffer_t *strbuff)
|
||||
|
||||
void strbuffer_close(strbuffer_t *strbuff)
|
||||
{
|
||||
free(strbuff->value);
|
||||
jsonp_free(strbuff->value);
|
||||
strbuff->size = 0;
|
||||
strbuff->length = 0;
|
||||
strbuff->value = NULL;
|
||||
@@ -64,13 +65,19 @@ int strbuffer_append_byte(strbuffer_t *strbuff, char byte)
|
||||
return strbuffer_append_bytes(strbuff, &byte, 1);
|
||||
}
|
||||
|
||||
int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, int size)
|
||||
int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size)
|
||||
{
|
||||
if(strbuff->length + size >= strbuff->size)
|
||||
if(size >= strbuff->size - strbuff->length)
|
||||
{
|
||||
size_t new_size;
|
||||
char *new_value;
|
||||
|
||||
/* avoid integer overflow */
|
||||
if (strbuff->size > STRBUFFER_SIZE_MAX / STRBUFFER_FACTOR
|
||||
|| size > STRBUFFER_SIZE_MAX - 1
|
||||
|| strbuff->length > STRBUFFER_SIZE_MAX - 1 - size)
|
||||
return -1;
|
||||
|
||||
new_size = max(strbuff->size * STRBUFFER_FACTOR,
|
||||
strbuff->length + size + 1);
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
typedef struct {
|
||||
char *value;
|
||||
int length; /* bytes used */
|
||||
int size; /* bytes allocated */
|
||||
size_t length; /* bytes used */
|
||||
size_t size; /* bytes allocated */
|
||||
} strbuffer_t;
|
||||
|
||||
int strbuffer_init(strbuffer_t *strbuff);
|
||||
@@ -24,7 +24,7 @@ char *strbuffer_steal_value(strbuffer_t *strbuff);
|
||||
|
||||
int strbuffer_append(strbuffer_t *strbuff, const char *string);
|
||||
int strbuffer_append_byte(strbuffer_t *strbuff, char byte);
|
||||
int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, int size);
|
||||
int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size);
|
||||
|
||||
char strbuffer_pop(strbuffer_t *strbuff);
|
||||
|
||||
|
||||
129
src/strconv.c
Normal file
129
src/strconv.c
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "jansson_private.h"
|
||||
#include "strbuffer.h"
|
||||
|
||||
#if JSON_HAVE_LOCALECONV
|
||||
#include <locale.h>
|
||||
|
||||
/*
|
||||
- This code assumes that the decimal separator is exactly one
|
||||
character.
|
||||
|
||||
- If setlocale() is called by another thread between the call to
|
||||
localeconv() and the call to sprintf() or strtod(), the result may
|
||||
be wrong. setlocale() is not thread-safe and should not be used
|
||||
this way. Multi-threaded programs should use uselocale() instead.
|
||||
*/
|
||||
|
||||
static void to_locale(strbuffer_t *strbuffer)
|
||||
{
|
||||
const char *point;
|
||||
char *pos;
|
||||
|
||||
point = localeconv()->decimal_point;
|
||||
if(*point == '.') {
|
||||
/* No conversion needed */
|
||||
return;
|
||||
}
|
||||
|
||||
pos = strchr(strbuffer->value, '.');
|
||||
if(pos)
|
||||
*pos = *point;
|
||||
}
|
||||
|
||||
static void from_locale(char *buffer)
|
||||
{
|
||||
const char *point;
|
||||
char *pos;
|
||||
|
||||
point = localeconv()->decimal_point;
|
||||
if(*point == '.') {
|
||||
/* No conversion needed */
|
||||
return;
|
||||
}
|
||||
|
||||
pos = strchr(buffer, *point);
|
||||
if(pos)
|
||||
*pos = '.';
|
||||
}
|
||||
#endif
|
||||
|
||||
int jsonp_strtod(strbuffer_t *strbuffer, double *out)
|
||||
{
|
||||
double value;
|
||||
char *end;
|
||||
|
||||
#if JSON_HAVE_LOCALECONV
|
||||
to_locale(strbuffer);
|
||||
#endif
|
||||
|
||||
errno = 0;
|
||||
value = strtod(strbuffer->value, &end);
|
||||
assert(end == strbuffer->value + strbuffer->length);
|
||||
|
||||
if(errno == ERANGE && value != 0) {
|
||||
/* Overflow */
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int jsonp_dtostr(char *buffer, size_t size, double value)
|
||||
{
|
||||
int ret;
|
||||
char *start, *end;
|
||||
size_t length;
|
||||
|
||||
ret = snprintf(buffer, size, "%.17g", value);
|
||||
if(ret < 0)
|
||||
return -1;
|
||||
|
||||
length = (size_t)ret;
|
||||
if(length >= size)
|
||||
return -1;
|
||||
|
||||
#if JSON_HAVE_LOCALECONV
|
||||
from_locale(buffer);
|
||||
#endif
|
||||
|
||||
/* Make sure there's a dot or 'e' in the output. Otherwise
|
||||
a real is converted to an integer when decoding */
|
||||
if(strchr(buffer, '.') == NULL &&
|
||||
strchr(buffer, 'e') == NULL)
|
||||
{
|
||||
if(length + 3 >= size) {
|
||||
/* No space to append ".0" */
|
||||
return -1;
|
||||
}
|
||||
buffer[length] = '.';
|
||||
buffer[length + 1] = '0';
|
||||
buffer[length + 2] = '\0';
|
||||
length += 2;
|
||||
}
|
||||
|
||||
/* Remove leading '+' from positive exponent. Also remove leading
|
||||
zeros from exponents (added by some printf() implementations) */
|
||||
start = strchr(buffer, 'e');
|
||||
if(start) {
|
||||
start++;
|
||||
end = start + 1;
|
||||
|
||||
if(*start == '-')
|
||||
start++;
|
||||
|
||||
while(*end == '0')
|
||||
end++;
|
||||
|
||||
if(end != start) {
|
||||
memmove(start, end, length - (size_t)(end - buffer));
|
||||
length -= (size_t)(end - start);
|
||||
}
|
||||
}
|
||||
|
||||
return (int)length;
|
||||
}
|
||||
181
src/value.c
181
src/value.c
@@ -26,39 +26,6 @@ static JSON_INLINE void json_init(json_t *json, json_type type)
|
||||
|
||||
/*** object ***/
|
||||
|
||||
/* This macro just returns a pointer that's a few bytes backwards from
|
||||
string. This makes it possible to pass a pointer to object_key_t
|
||||
when only the string inside it is used, without actually creating
|
||||
an object_key_t instance. */
|
||||
#define string_to_key(string) container_of(string, object_key_t, key)
|
||||
|
||||
size_t jsonp_hash_key(const void *ptr)
|
||||
{
|
||||
const char *str = ((const object_key_t *)ptr)->key;
|
||||
|
||||
size_t hash = 5381;
|
||||
size_t c;
|
||||
|
||||
while((c = (size_t)*str))
|
||||
{
|
||||
hash = ((hash << 5) + hash) + c;
|
||||
str++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
int jsonp_key_equal(const void *ptr1, const void *ptr2)
|
||||
{
|
||||
return strcmp(((const object_key_t *)ptr1)->key,
|
||||
((const object_key_t *)ptr2)->key) == 0;
|
||||
}
|
||||
|
||||
static void value_decref(void *value)
|
||||
{
|
||||
json_decref((json_t *)value);
|
||||
}
|
||||
|
||||
json_t *json_object(void)
|
||||
{
|
||||
json_object_t *object = jsonp_malloc(sizeof(json_object_t));
|
||||
@@ -66,9 +33,7 @@ json_t *json_object(void)
|
||||
return NULL;
|
||||
json_init(&object->json, JSON_OBJECT);
|
||||
|
||||
if(hashtable_init(&object->hashtable,
|
||||
jsonp_hash_key, jsonp_key_equal,
|
||||
jsonp_free, value_decref))
|
||||
if(hashtable_init(&object->hashtable))
|
||||
{
|
||||
jsonp_free(object);
|
||||
return NULL;
|
||||
@@ -91,7 +56,7 @@ size_t json_object_size(const json_t *json)
|
||||
json_object_t *object;
|
||||
|
||||
if(!json_is_object(json))
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
object = json_to_object(json);
|
||||
return object->hashtable.size;
|
||||
@@ -105,35 +70,24 @@ json_t *json_object_get(const json_t *json, const char *key)
|
||||
return NULL;
|
||||
|
||||
object = json_to_object(json);
|
||||
return hashtable_get(&object->hashtable, string_to_key(key));
|
||||
return hashtable_get(&object->hashtable, key);
|
||||
}
|
||||
|
||||
int json_object_set_new_nocheck(json_t *json, const char *key, json_t *value)
|
||||
{
|
||||
json_object_t *object;
|
||||
object_key_t *k;
|
||||
|
||||
if(!key || !value)
|
||||
if(!value)
|
||||
return -1;
|
||||
|
||||
if(!json_is_object(json) || json == value)
|
||||
if(!key || !json_is_object(json) || json == value)
|
||||
{
|
||||
json_decref(value);
|
||||
return -1;
|
||||
}
|
||||
object = json_to_object(json);
|
||||
|
||||
/* offsetof(...) returns the size of object_key_t without the
|
||||
last, flexible member. This way, the correct amount is
|
||||
allocated. */
|
||||
k = jsonp_malloc(offsetof(object_key_t, key) + strlen(key) + 1);
|
||||
if(!k)
|
||||
return -1;
|
||||
|
||||
k->serial = object->serial++;
|
||||
strcpy(k->key, key);
|
||||
|
||||
if(hashtable_set(&object->hashtable, k, value))
|
||||
if(hashtable_set(&object->hashtable, key, object->serial++, value))
|
||||
{
|
||||
json_decref(value);
|
||||
return -1;
|
||||
@@ -161,7 +115,7 @@ int json_object_del(json_t *json, const char *key)
|
||||
return -1;
|
||||
|
||||
object = json_to_object(json);
|
||||
return hashtable_del(&object->hashtable, string_to_key(key));
|
||||
return hashtable_del(&object->hashtable, key);
|
||||
}
|
||||
|
||||
int json_object_clear(json_t *json)
|
||||
@@ -172,30 +126,56 @@ int json_object_clear(json_t *json)
|
||||
return -1;
|
||||
|
||||
object = json_to_object(json);
|
||||
|
||||
hashtable_clear(&object->hashtable);
|
||||
object->serial = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_object_update(json_t *object, json_t *other)
|
||||
{
|
||||
void *iter;
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
if(!json_is_object(object) || !json_is_object(other))
|
||||
return -1;
|
||||
|
||||
iter = json_object_iter(other);
|
||||
while(iter) {
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
key = json_object_iter_key(iter);
|
||||
value = json_object_iter_value(iter);
|
||||
|
||||
json_object_foreach(other, key, value) {
|
||||
if(json_object_set_nocheck(object, key, value))
|
||||
return -1;
|
||||
}
|
||||
|
||||
iter = json_object_iter_next(other, iter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_object_update_existing(json_t *object, json_t *other)
|
||||
{
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
if(!json_is_object(object) || !json_is_object(other))
|
||||
return -1;
|
||||
|
||||
json_object_foreach(other, key, value) {
|
||||
if(json_object_get(object, key))
|
||||
json_object_set_nocheck(object, key, value);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_object_update_missing(json_t *object, json_t *other)
|
||||
{
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
if(!json_is_object(object) || !json_is_object(other))
|
||||
return -1;
|
||||
|
||||
json_object_foreach(other, key, value) {
|
||||
if(!json_object_get(object, key))
|
||||
json_object_set_nocheck(object, key, value);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -220,7 +200,7 @@ void *json_object_iter_at(json_t *json, const char *key)
|
||||
return NULL;
|
||||
|
||||
object = json_to_object(json);
|
||||
return hashtable_iter_at(&object->hashtable, string_to_key(key));
|
||||
return hashtable_iter_at(&object->hashtable, key);
|
||||
}
|
||||
|
||||
void *json_object_iter_next(json_t *json, void *iter)
|
||||
@@ -234,20 +214,12 @@ void *json_object_iter_next(json_t *json, void *iter)
|
||||
return hashtable_iter_next(&object->hashtable, iter);
|
||||
}
|
||||
|
||||
const object_key_t *jsonp_object_iter_fullkey(void *iter)
|
||||
{
|
||||
if(!iter)
|
||||
return NULL;
|
||||
|
||||
return hashtable_iter_key(iter);
|
||||
}
|
||||
|
||||
const char *json_object_iter_key(void *iter)
|
||||
{
|
||||
if(!iter)
|
||||
return NULL;
|
||||
|
||||
return jsonp_object_iter_fullkey(iter)->key;
|
||||
return hashtable_iter_key(iter);
|
||||
}
|
||||
|
||||
json_t *json_object_iter_value(void *iter)
|
||||
@@ -260,38 +232,34 @@ json_t *json_object_iter_value(void *iter)
|
||||
|
||||
int json_object_iter_set_new(json_t *json, void *iter, json_t *value)
|
||||
{
|
||||
json_object_t *object;
|
||||
|
||||
if(!json_is_object(json) || !iter || !value)
|
||||
return -1;
|
||||
|
||||
object = json_to_object(json);
|
||||
hashtable_iter_set(&object->hashtable, iter, value);
|
||||
|
||||
hashtable_iter_set(iter, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *json_object_key_to_iter(const char *key)
|
||||
{
|
||||
if(!key)
|
||||
return NULL;
|
||||
|
||||
return hashtable_key_to_iter(key);
|
||||
}
|
||||
|
||||
static int json_object_equal(json_t *object1, json_t *object2)
|
||||
{
|
||||
void *iter;
|
||||
const char *key;
|
||||
json_t *value1, *value2;
|
||||
|
||||
if(json_object_size(object1) != json_object_size(object2))
|
||||
return 0;
|
||||
|
||||
iter = json_object_iter(object1);
|
||||
while(iter)
|
||||
{
|
||||
const char *key;
|
||||
json_t *value1, *value2;
|
||||
|
||||
key = json_object_iter_key(iter);
|
||||
value1 = json_object_iter_value(iter);
|
||||
json_object_foreach(object1, key, value1) {
|
||||
value2 = json_object_get(object2, key);
|
||||
|
||||
if(!json_equal(value1, value2))
|
||||
return 0;
|
||||
|
||||
iter = json_object_iter_next(object1, iter);
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -300,50 +268,34 @@ static int json_object_equal(json_t *object1, json_t *object2)
|
||||
static json_t *json_object_copy(json_t *object)
|
||||
{
|
||||
json_t *result;
|
||||
void *iter;
|
||||
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
result = json_object();
|
||||
if(!result)
|
||||
return NULL;
|
||||
|
||||
iter = json_object_iter(object);
|
||||
while(iter)
|
||||
{
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
key = json_object_iter_key(iter);
|
||||
value = json_object_iter_value(iter);
|
||||
json_object_foreach(object, key, value)
|
||||
json_object_set_nocheck(result, key, value);
|
||||
|
||||
iter = json_object_iter_next(object, iter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static json_t *json_object_deep_copy(json_t *object)
|
||||
{
|
||||
json_t *result;
|
||||
void *iter;
|
||||
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
result = json_object();
|
||||
if(!result)
|
||||
return NULL;
|
||||
|
||||
iter = json_object_iter(object);
|
||||
while(iter)
|
||||
{
|
||||
const char *key;
|
||||
json_t *value;
|
||||
|
||||
key = json_object_iter_key(iter);
|
||||
value = json_object_iter_value(iter);
|
||||
json_object_foreach(object, key, value)
|
||||
json_object_set_new_nocheck(result, key, json_deep_copy(value));
|
||||
|
||||
iter = json_object_iter_next(object, iter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -689,6 +641,9 @@ int json_string_set_nocheck(json_t *json, const char *value)
|
||||
char *dup;
|
||||
json_string_t *string;
|
||||
|
||||
if(!json_is_string(json) || !value)
|
||||
return -1;
|
||||
|
||||
dup = jsonp_strdup(value);
|
||||
if(!dup)
|
||||
return -1;
|
||||
|
||||
2
test/.gitignore
vendored
2
test/.gitignore
vendored
@@ -4,8 +4,10 @@ suites/api/test_array
|
||||
suites/api/test_copy
|
||||
suites/api/test_cpp
|
||||
suites/api/test_dump
|
||||
suites/api/test_dump_callback
|
||||
suites/api/test_equal
|
||||
suites/api/test_load
|
||||
suites/api/test_loadb
|
||||
suites/api/test_memory_funcs
|
||||
suites/api/test_number
|
||||
suites/api/test_object
|
||||
|
||||
@@ -5,12 +5,25 @@
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#if HAVE_LOCALE_H
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
#if _WIN32
|
||||
#include <io.h> /* for _setmode() */
|
||||
#include <fcntl.h> /* for _O_BINARY */
|
||||
#endif
|
||||
|
||||
static int getenv_int(const char *name)
|
||||
{
|
||||
char *value, *end;
|
||||
@@ -55,11 +68,22 @@ int main(int argc, char *argv[])
|
||||
json_t *json;
|
||||
json_error_t error;
|
||||
|
||||
#if HAVE_SETLOCALE
|
||||
setlocale(LC_ALL, "");
|
||||
#endif
|
||||
|
||||
if(argc != 1) {
|
||||
fprintf(stderr, "usage: %s\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/* On Windows, set stdout and stderr to binary mode to avoid
|
||||
outputting DOS line terminators */
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
_setmode(_fileno(stderr), _O_BINARY);
|
||||
#endif
|
||||
|
||||
indent = getenv_int("JSON_INDENT");
|
||||
if(indent < 0 || indent > 255) {
|
||||
fprintf(stderr, "invalid value for JSON_INDENT: %d\n", indent);
|
||||
|
||||
@@ -30,31 +30,46 @@ for test_path in $suite_srcdir/*; do
|
||||
rm -rf $test_log
|
||||
mkdir -p $test_log
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "$test_name... "
|
||||
printf '%s... ' "$test_name"
|
||||
fi
|
||||
|
||||
if run_test; then
|
||||
# Success
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "ok"
|
||||
else
|
||||
echo -n "."
|
||||
fi
|
||||
rm -rf $test_log
|
||||
else
|
||||
# Failure
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "FAILED"
|
||||
else
|
||||
echo -n "F"
|
||||
fi
|
||||
run_test
|
||||
case $? in
|
||||
0)
|
||||
# Success
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
printf 'ok\n'
|
||||
else
|
||||
printf '.'
|
||||
fi
|
||||
rm -rf $test_log
|
||||
;;
|
||||
|
||||
[ $STOP -eq 1 ] && break
|
||||
fi
|
||||
77)
|
||||
# Skip
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
printf 'skipped\n'
|
||||
else
|
||||
printf 'S'
|
||||
fi
|
||||
rm -rf $test_log
|
||||
;;
|
||||
|
||||
*)
|
||||
# Failure
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
printf 'FAILED\n'
|
||||
else
|
||||
printf 'F'
|
||||
fi
|
||||
|
||||
[ $STOP -eq 1 ] && break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $VERBOSE -eq 0 ]; then
|
||||
echo
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
if [ -n "$(ls -A $suite_log)" ]; then
|
||||
|
||||
@@ -4,8 +4,10 @@ check_PROGRAMS = \
|
||||
test_array \
|
||||
test_copy \
|
||||
test_dump \
|
||||
test_dump_callback \
|
||||
test_equal \
|
||||
test_load \
|
||||
test_loadb \
|
||||
test_memory_funcs \
|
||||
test_number \
|
||||
test_object \
|
||||
@@ -16,7 +18,9 @@ check_PROGRAMS = \
|
||||
test_array_SOURCES = test_array.c util.h
|
||||
test_copy_SOURCES = test_copy.c util.h
|
||||
test_dump_SOURCES = test_dump.c util.h
|
||||
test_dump_callback_SOURCES = test_dump_callback.c util.h
|
||||
test_load_SOURCES = test_load.c util.h
|
||||
test_loadb_SOURCES = test_loadb.c util.h
|
||||
test_memory_funcs_SOURCES = test_memory_funcs.c util.h
|
||||
test_number_SOURCES = test_number.c util.h
|
||||
test_object_SOURCES = test_object.c util.h
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This tests checks that the libjansson.so exports the correct
|
||||
# symbols.
|
||||
# This test checks that libjansson.so exports the correct symbols.
|
||||
|
||||
# The list of symbols that the shared object should export
|
||||
sort >$test_log/exports <<EOF
|
||||
@@ -38,18 +37,23 @@ json_object_set_new_nocheck
|
||||
json_object_del
|
||||
json_object_clear
|
||||
json_object_update
|
||||
json_object_update_existing
|
||||
json_object_update_missing
|
||||
json_object_iter
|
||||
json_object_iter_at
|
||||
json_object_iter_next
|
||||
json_object_iter_key
|
||||
json_object_iter_value
|
||||
json_object_iter_set_new
|
||||
json_object_key_to_iter
|
||||
json_dumps
|
||||
json_dumpf
|
||||
json_dump_file
|
||||
json_dump_callback
|
||||
json_loads
|
||||
json_loadf
|
||||
json_load_file
|
||||
json_loadb
|
||||
json_equal
|
||||
json_copy
|
||||
json_deep_copy
|
||||
@@ -89,7 +93,10 @@ EOF
|
||||
|
||||
SOFILE="../src/.libs/libjansson.so"
|
||||
|
||||
nm -D $SOFILE | grep ' T ' | cut -d' ' -f3 | sort >$test_log/output
|
||||
nm -D $SOFILE >/dev/null >$test_log/symbols 2>/dev/null \
|
||||
|| exit 77 # Skip if "nm -D" doesn't seem to work
|
||||
|
||||
grep ' T ' $test_log/symbols | cut -d' ' -f3 | sort >$test_log/output
|
||||
|
||||
if ! cmp -s $test_log/exports $test_log/output; then
|
||||
diff -u $test_log/exports $test_log/output >&2
|
||||
|
||||
@@ -387,7 +387,7 @@ static void test_circular()
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
test_misc();
|
||||
test_insert();
|
||||
@@ -395,6 +395,4 @@ int main()
|
||||
test_clear();
|
||||
test_extend();
|
||||
test_circular();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ static void test_deep_copy_object(void)
|
||||
json_decref(copy);
|
||||
}
|
||||
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
test_copy_simple();
|
||||
test_deep_copy_simple();
|
||||
@@ -315,5 +315,4 @@ int main()
|
||||
test_deep_copy_array();
|
||||
test_copy_object();
|
||||
test_deep_copy_object();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
#include <string.h>
|
||||
#include "util.h"
|
||||
|
||||
int main()
|
||||
static void encode_twice()
|
||||
{
|
||||
/* Encode an empty object/array, add an item, encode again */
|
||||
|
||||
json_t *json;
|
||||
char *result;
|
||||
|
||||
/* Encode an empty object/array, add an item, encode again */
|
||||
|
||||
json = json_object();
|
||||
result = json_dumps(json, 0);
|
||||
if(!result || strcmp(result, "{}"))
|
||||
@@ -43,7 +43,10 @@ int main()
|
||||
free(result);
|
||||
|
||||
json_decref(json);
|
||||
}
|
||||
|
||||
static void circular_references()
|
||||
{
|
||||
/* Construct a JSON object/array with a circular reference:
|
||||
|
||||
object: {"a": {"b": {"c": <circular reference to $.a>}}}
|
||||
@@ -51,6 +54,10 @@ int main()
|
||||
|
||||
Encode it, remove the circular reference and encode again.
|
||||
*/
|
||||
|
||||
json_t *json;
|
||||
char *result;
|
||||
|
||||
json = json_object();
|
||||
json_object_set_new(json, "a", json_object());
|
||||
json_object_set_new(json_object_get(json, "a"), "b", json_object());
|
||||
@@ -86,6 +93,49 @@ int main()
|
||||
free(result);
|
||||
|
||||
json_decref(json);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void encode_other_than_array_or_object()
|
||||
{
|
||||
/* Encoding anything other than array or object should only
|
||||
* succeed if the JSON_ENCODE_ANY flag is used */
|
||||
|
||||
json_t *json;
|
||||
FILE *fp = NULL;
|
||||
char *result;
|
||||
|
||||
json = json_string("foo");
|
||||
if(json_dumps(json, 0) != NULL)
|
||||
fail("json_dumps encoded a string!");
|
||||
if(json_dumpf(json, fp, 0) == 0)
|
||||
fail("json_dumpf encoded a string!");
|
||||
|
||||
result = json_dumps(json, JSON_ENCODE_ANY);
|
||||
if(!result || strcmp(result, "\"foo\"") != 0)
|
||||
fail("json_dumps failed to encode a string with JSON_ENCODE_ANY");
|
||||
|
||||
free(result);
|
||||
json_decref(json);
|
||||
|
||||
json = json_integer(42);
|
||||
if(json_dumps(json, 0) != NULL)
|
||||
fail("json_dumps encoded an integer!");
|
||||
if(json_dumpf(json, fp, 0) == 0)
|
||||
fail("json_dumpf encoded an integer!");
|
||||
|
||||
result = json_dumps(json, JSON_ENCODE_ANY);
|
||||
if(!result || strcmp(result, "42") != 0)
|
||||
fail("json_dumps failed to encode an integer with JSON_ENCODE_ANY");
|
||||
|
||||
free(result);
|
||||
json_decref(json);
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void run_tests()
|
||||
{
|
||||
encode_twice();
|
||||
circular_references();
|
||||
encode_other_than_array_or_object();
|
||||
}
|
||||
|
||||
81
test/suites/api/test_dump_callback.c
Normal file
81
test/suites/api/test_dump_callback.c
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011 Petri Lehtinen <petri@digip.org>
|
||||
*
|
||||
* Jansson is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <jansson.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "util.h"
|
||||
|
||||
struct my_sink {
|
||||
char *buf;
|
||||
size_t off;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
static int my_writer(const char *buffer, size_t len, void *data) {
|
||||
struct my_sink *s = data;
|
||||
if (len > s->cap - s->off) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(s->buf + s->off, buffer, len);
|
||||
s->off += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_tests()
|
||||
{
|
||||
struct my_sink s;
|
||||
json_t *json;
|
||||
const char str[] = "[\"A\", {\"B\": \"C\", \"e\": false}, 1, null, \"foo\"]";
|
||||
char *dumped_to_string;
|
||||
|
||||
json = json_loads(str, 0, NULL);
|
||||
if(!json) {
|
||||
fail("json_loads failed");
|
||||
}
|
||||
|
||||
dumped_to_string = json_dumps(json, 0);
|
||||
if (!dumped_to_string) {
|
||||
json_decref(json);
|
||||
fail("json_dumps failed");
|
||||
}
|
||||
|
||||
s.off = 0;
|
||||
s.cap = strlen(dumped_to_string);
|
||||
s.buf = malloc(s.cap);
|
||||
if (!s.buf) {
|
||||
json_decref(json);
|
||||
free(dumped_to_string);
|
||||
fail("malloc failed");
|
||||
}
|
||||
|
||||
if (json_dump_callback(json, my_writer, &s, 0) == -1) {
|
||||
json_decref(json);
|
||||
free(dumped_to_string);
|
||||
free(s.buf);
|
||||
fail("json_dump_callback failed on an exact-length sink buffer");
|
||||
}
|
||||
|
||||
if (strncmp(dumped_to_string, s.buf, s.off) != 0) {
|
||||
json_decref(json);
|
||||
free(dumped_to_string);
|
||||
free(s.buf);
|
||||
fail("json_dump_callback and json_dumps did not produce identical output");
|
||||
}
|
||||
|
||||
s.off = 1;
|
||||
if (json_dump_callback(json, my_writer, &s, 0) != -1) {
|
||||
json_decref(json);
|
||||
free(dumped_to_string);
|
||||
free(s.buf);
|
||||
fail("json_dump_callback succeeded on a short buffer when it should have failed");
|
||||
}
|
||||
|
||||
json_decref(json);
|
||||
free(dumped_to_string);
|
||||
free(s.buf);
|
||||
}
|
||||
@@ -180,11 +180,10 @@ static void test_equal_complex()
|
||||
/* TODO: There's no negative test case here */
|
||||
}
|
||||
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
test_equal_simple();
|
||||
test_equal_array();
|
||||
test_equal_object();
|
||||
test_equal_complex();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9,16 +9,118 @@
|
||||
#include <string.h>
|
||||
#include "util.h"
|
||||
|
||||
int main()
|
||||
static void file_not_found()
|
||||
{
|
||||
json_t *json;
|
||||
json_error_t error;
|
||||
|
||||
json = json_load_file("/path/to/nonexistent/file.json", 0, &error);
|
||||
if(json)
|
||||
fail("json_load_file returned non-NULL for a nonexistent file");
|
||||
if(error.line != -1)
|
||||
fail("json_load_file returned an invalid line number");
|
||||
if(strcmp(error.text, "unable to open /path/to/nonexistent/file.json: No such file or directory") != 0)
|
||||
fail("json_load_file returned an invalid error message");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void reject_duplicates()
|
||||
{
|
||||
json_error_t error;
|
||||
|
||||
if(json_loads("{\"foo\": 1, \"foo\": 2}", JSON_REJECT_DUPLICATES, &error))
|
||||
fail("json_loads did not detect a duplicate key");
|
||||
check_error("duplicate object key near '\"foo\"'", "<string>", 1, 16, 16);
|
||||
}
|
||||
|
||||
static void disable_eof_check()
|
||||
{
|
||||
json_error_t error;
|
||||
json_t *json;
|
||||
|
||||
const char *text = "{\"foo\": 1} garbage";
|
||||
|
||||
if(json_loads(text, 0, &error))
|
||||
fail("json_loads did not detect garbage after JSON text");
|
||||
check_error("end of file expected near 'garbage'", "<string>", 1, 18, 18);
|
||||
|
||||
json = json_loads(text, JSON_DISABLE_EOF_CHECK, &error);
|
||||
if(!json)
|
||||
fail("json_loads failed with JSON_DISABLE_EOF_CHECK");
|
||||
|
||||
json_decref(json);
|
||||
}
|
||||
|
||||
static void decode_any()
|
||||
{
|
||||
json_t *json;
|
||||
json_error_t error;
|
||||
|
||||
json = json_loads("\"foo\"", JSON_DECODE_ANY, &error);
|
||||
if (!json || !json_is_string(json))
|
||||
fail("json_load decoded any failed - string");
|
||||
json_decref(json);
|
||||
|
||||
json = json_loads("42", JSON_DECODE_ANY, &error);
|
||||
if (!json || !json_is_integer(json))
|
||||
fail("json_load decoded any failed - integer");
|
||||
json_decref(json);
|
||||
|
||||
json = json_loads("true", JSON_DECODE_ANY, &error);
|
||||
if (!json || !json_is_true(json))
|
||||
fail("json_load decoded any failed - boolean");
|
||||
json_decref(json);
|
||||
|
||||
json = json_loads("null", JSON_DECODE_ANY, &error);
|
||||
if (!json || !json_is_null(json))
|
||||
fail("json_load decoded any failed - null");
|
||||
json_decref(json);
|
||||
}
|
||||
|
||||
static void load_wrong_args()
|
||||
{
|
||||
json_t *json;
|
||||
json_error_t error;
|
||||
|
||||
json = json_loads(NULL, 0, &error);
|
||||
if (json)
|
||||
fail("json_loads should return NULL if the first argument is NULL");
|
||||
|
||||
json = json_loadb(NULL, 0, 0, &error);
|
||||
if (json)
|
||||
fail("json_loadb should return NULL if the first argument is NULL");
|
||||
|
||||
json = json_loadf(NULL, 0, &error);
|
||||
if (json)
|
||||
fail("json_loadf should return NULL if the first argument is NULL");
|
||||
|
||||
json = json_load_file(NULL, 0, &error);
|
||||
if (json)
|
||||
fail("json_loadf should return NULL if the first argument is NULL");
|
||||
}
|
||||
|
||||
static void position()
|
||||
{
|
||||
json_t *json;
|
||||
size_t flags = JSON_DISABLE_EOF_CHECK;
|
||||
json_error_t error;
|
||||
|
||||
json = json_loads("{\"foo\": \"bar\"}", 0, &error);
|
||||
if(error.position != 14)
|
||||
fail("json_loads returned a wrong position");
|
||||
json_decref(json);
|
||||
|
||||
json = json_loads("{\"foo\": \"bar\"} baz quux", flags, &error);
|
||||
if(error.position != 14)
|
||||
fail("json_loads returned a wrong position");
|
||||
json_decref(json);
|
||||
}
|
||||
|
||||
static void run_tests()
|
||||
{
|
||||
file_not_found();
|
||||
reject_duplicates();
|
||||
disable_eof_check();
|
||||
decode_any();
|
||||
load_wrong_args();
|
||||
position();
|
||||
}
|
||||
|
||||
36
test/suites/api/test_loadb.c
Normal file
36
test/suites/api/test_loadb.c
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011 Petri Lehtinen <petri@digip.org>
|
||||
*
|
||||
* Jansson is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <jansson.h>
|
||||
#include <string.h>
|
||||
#include "util.h"
|
||||
|
||||
static void run_tests()
|
||||
{
|
||||
json_t *json;
|
||||
json_error_t error;
|
||||
const char str[] = "[\"A\", {\"B\": \"C\"}, 1, 2, 3]garbage";
|
||||
size_t len = strlen(str) - strlen("garbage");
|
||||
|
||||
json = json_loadb(str, len, 0, &error);
|
||||
if(!json) {
|
||||
fail("json_loadb failed on a valid JSON buffer");
|
||||
}
|
||||
json_decref(json);
|
||||
|
||||
json = json_loadb(str, len - 1, 0, &error);
|
||||
if (json) {
|
||||
json_decref(json);
|
||||
fail("json_loadb should have failed on an incomplete buffer, but it didn't");
|
||||
}
|
||||
if(error.line != 1) {
|
||||
fail("json_loadb returned an invalid line number on fail");
|
||||
}
|
||||
if(strcmp(error.text, "']' expected near end of file") != 0) {
|
||||
fail("json_loadb returned an invalid error message for an unclosed top-level array");
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ static void test_simple()
|
||||
json_set_alloc_funcs(my_malloc, my_free);
|
||||
create_and_free_complex_object();
|
||||
|
||||
if(malloc_called != 27 || free_called != 27)
|
||||
if(malloc_called != 20 || free_called != 20)
|
||||
fail("Custom allocation failed");
|
||||
}
|
||||
|
||||
@@ -75,10 +75,8 @@ static void test_secure_funcs(void)
|
||||
create_and_free_complex_object();
|
||||
}
|
||||
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
test_simple();
|
||||
test_secure_funcs();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <jansson.h>
|
||||
#include "util.h"
|
||||
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
json_t *integer, *real;
|
||||
int i;
|
||||
@@ -39,6 +39,4 @@ int main()
|
||||
|
||||
json_decref(integer);
|
||||
json_decref(real);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,48 @@ static void test_update()
|
||||
json_decref(object);
|
||||
}
|
||||
|
||||
static void test_conditional_updates()
|
||||
{
|
||||
json_t *object, *other;
|
||||
|
||||
object = json_pack("{sisi}", "foo", 1, "bar", 2);
|
||||
other = json_pack("{sisi}", "foo", 3, "baz", 4);
|
||||
|
||||
if(json_object_update_existing(object, other))
|
||||
fail("json_object_update_existing failed");
|
||||
|
||||
if(json_object_size(object) != 2)
|
||||
fail("json_object_update_existing added new items");
|
||||
|
||||
if(json_integer_value(json_object_get(object, "foo")) != 3)
|
||||
fail("json_object_update_existing failed to update existing key");
|
||||
|
||||
if(json_integer_value(json_object_get(object, "bar")) != 2)
|
||||
fail("json_object_update_existing updated wrong key");
|
||||
|
||||
json_decref(object);
|
||||
|
||||
object = json_pack("{sisi}", "foo", 1, "bar", 2);
|
||||
|
||||
if(json_object_update_missing(object, other))
|
||||
fail("json_object_update_missing failed");
|
||||
|
||||
if(json_object_size(object) != 3)
|
||||
fail("json_object_update_missing didn't add new items");
|
||||
|
||||
if(json_integer_value(json_object_get(object, "foo")) != 1)
|
||||
fail("json_object_update_missing updated existing key");
|
||||
|
||||
if(json_integer_value(json_object_get(object, "bar")) != 2)
|
||||
fail("json_object_update_missing updated wrong key");
|
||||
|
||||
if(json_integer_value(json_object_get(object, "baz")) != 4)
|
||||
fail("json_object_update_missing didn't add new items");
|
||||
|
||||
json_decref(object);
|
||||
json_decref(other);
|
||||
}
|
||||
|
||||
static void test_circular()
|
||||
{
|
||||
json_t *object1, *object2;
|
||||
@@ -437,15 +479,33 @@ static void test_preserve_order()
|
||||
json_decref(object);
|
||||
}
|
||||
|
||||
int main()
|
||||
static void test_foreach()
|
||||
{
|
||||
const char *key;
|
||||
json_t *object1, *object2, *value;
|
||||
|
||||
object1 = json_pack("{sisisi}", "foo", 1, "bar", 2, "baz", 3);
|
||||
object2 = json_object();
|
||||
|
||||
json_object_foreach(object1, key, value)
|
||||
json_object_set(object2, key, value);
|
||||
|
||||
if(!json_equal(object1, object2))
|
||||
fail("json_object_foreach failed to iterate all key-value pairs");
|
||||
|
||||
json_decref(object1);
|
||||
json_decref(object2);
|
||||
}
|
||||
|
||||
static void run_tests()
|
||||
{
|
||||
test_misc();
|
||||
test_clear();
|
||||
test_update();
|
||||
test_conditional_updates();
|
||||
test_circular();
|
||||
test_set_nocheck();
|
||||
test_iterators();
|
||||
test_preserve_order();
|
||||
|
||||
return 0;
|
||||
test_foreach();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <stdio.h>
|
||||
#include "util.h"
|
||||
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
json_t *value;
|
||||
int i;
|
||||
@@ -227,6 +227,4 @@ int main()
|
||||
if(json_pack_ex(&error, 0, "{s:s}", "foo", "\xff\xff"))
|
||||
fail("json_pack failed to catch invalid UTF-8 in a string");
|
||||
check_error("Invalid UTF-8 string", "<args>", 1, 4, 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "util.h"
|
||||
|
||||
/* Call the simple functions not covered by other tests of the public API */
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
json_t *value;
|
||||
|
||||
@@ -180,6 +180,4 @@ int main()
|
||||
json_incref(value);
|
||||
if(value->refcount != (size_t)-1)
|
||||
fail("refcounting null works incorrectly");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <stdio.h>
|
||||
#include "util.h"
|
||||
|
||||
int main()
|
||||
static void run_tests()
|
||||
{
|
||||
json_t *j, *j2;
|
||||
int i1, i2, i3;
|
||||
@@ -122,6 +122,13 @@ int main()
|
||||
fail("json_unpack simple array failed");
|
||||
json_decref(j);
|
||||
|
||||
/* object with many items & strict checking */
|
||||
j = json_pack("{s:i, s:i, s:i}", "a", 1, "b", 2, "c", 3);
|
||||
rv = json_unpack(j, "{s:i, s:i, s:i}", "a", &i1, "b", &i2, "c", &i3);
|
||||
if(rv || i1 != 1 || i2 != 2 || i3 != 3)
|
||||
fail("json_unpack object with many items failed");
|
||||
json_decref(j);
|
||||
|
||||
/*
|
||||
* Invalid cases
|
||||
*/
|
||||
@@ -285,7 +292,7 @@ int main()
|
||||
/* Unpack the same item twice */
|
||||
j = json_pack("{s:s, s:i}", "foo", "bar", "baz", 42);
|
||||
if(!json_unpack_ex(j, &error, 0, "{s:s,s:s!}", "foo", &s, "foo", &s))
|
||||
fail("json_unpack object with strict validation failed");
|
||||
fail("json_unpack object with strict validation failed");
|
||||
check_error("1 object item(s) left unpacked", "<validation>", 1, 10, 10);
|
||||
json_decref(j);
|
||||
|
||||
@@ -330,5 +337,37 @@ int main()
|
||||
check_error("1 array item(s) left unpacked", "<validation>", 1, 5, 5);
|
||||
json_decref(j);
|
||||
|
||||
return 0;
|
||||
/* Optional values */
|
||||
j = json_object();
|
||||
i1 = 0;
|
||||
if(json_unpack(j, "{s?i}", "foo", &i1))
|
||||
fail("json_unpack failed for optional key");
|
||||
if(i1 != 0)
|
||||
fail("json_unpack unpacked an optional key");
|
||||
json_decref(j);
|
||||
|
||||
i1 = 0;
|
||||
j = json_pack("{si}", "foo", 42);
|
||||
if(json_unpack(j, "{s?i}", "foo", &i1))
|
||||
fail("json_unpack failed for an optional value");
|
||||
if(i1 != 42)
|
||||
fail("json_unpack failed to unpack an optional value");
|
||||
json_decref(j);
|
||||
|
||||
j = json_object();
|
||||
i1 = i2 = i3 = 0;
|
||||
if(json_unpack(j, "{s?[ii]s?{s{si}}}",
|
||||
"foo", &i1, &i2,
|
||||
"bar", "baz", "quux", &i3))
|
||||
fail("json_unpack failed for complex optional values");
|
||||
if(i1 != 0 || i2 != 0 || i3 != 0)
|
||||
fail("json_unpack unexpectedly unpacked something");
|
||||
json_decref(j);
|
||||
|
||||
j = json_pack("{s{si}}", "foo", "bar", 42);
|
||||
if(json_unpack(j, "{s?{s?i}}", "foo", "bar", &i1))
|
||||
fail("json_unpack failed for complex optional values");
|
||||
if(i1 != 42)
|
||||
fail("json_unpack failed to unpack");
|
||||
json_decref(j);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,16 @@
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#if HAVE_LOCALE_H
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
#define failhdr fprintf(stderr, "%s:%s:%d: ", __FILE__, __FUNCTION__, __LINE__)
|
||||
@@ -52,4 +60,15 @@
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
|
||||
static void run_tests();
|
||||
|
||||
int main() {
|
||||
#ifdef HAVE_SETLOCALE
|
||||
setlocale(LC_ALL, "");
|
||||
#endif
|
||||
run_tests();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1 +1 @@
|
||||
[1e+22]
|
||||
[1e22]
|
||||
@@ -1 +1 @@
|
||||
[1.2299999999999999e+47]
|
||||
[1.2299999999999999e47]
|
||||
@@ -1 +1 @@
|
||||
[1.23456e+80]
|
||||
[1.23456e80]
|
||||
Reference in New Issue
Block a user