From c5475315abc2b92b1e7980d714ee955f71d16cc5 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Fri, 2 Mar 2018 14:14:50 +0100 Subject: [PATCH 01/12] Introduce ability to use already serialized objects When the `default` function returns a bytes object, include it verbatim into the serializaion. This allows users to store preserialized versions of their objects in a database and use rapidjson to generate structures that include them. If the `default` function returns an invalid JSON the resulting string returned by rapidjson will be in turn invalid. --- rapidjson.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/rapidjson.cpp b/rapidjson.cpp index 3741543..40893b6 100644 --- a/rapidjson.cpp +++ b/rapidjson.cpp @@ -2627,15 +2627,24 @@ dumps_internal( PyObject* retval = PyObject_CallFunctionObjArgs(defaultFn, object, NULL); if (retval == NULL) return false; - if (Py_EnterRecursiveCall(" while JSONifying default function result")) { + if (PyBytes_Check(retval)) { + Py_ssize_t l = PyBytes_Size(retval); + ASSERT_VALID_SIZE(l); + const char* s = PyBytes_AsString(retval); + writer->RawValue(s, (SizeType) l, kStringType); Py_DECREF(retval); - return false; } - bool r = RECURSE(retval); - Py_LeaveRecursiveCall(); - Py_DECREF(retval); - if (!r) - return false; + else { + if (Py_EnterRecursiveCall(" while JSONifying default function result")) { + Py_DECREF(retval); + return false; + } + bool r = RECURSE(retval); + Py_LeaveRecursiveCall(); + Py_DECREF(retval); + if (!r) + return false; + } } else { PyErr_Format(PyExc_TypeError, "%R is not JSON serializable", object); From 6fffcc07994b37f979c4a33daad55bc6ded6df06 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Fri, 2 Mar 2018 18:23:14 +0100 Subject: [PATCH 02/12] Update docs and include memory leak test for preserialized JSON --- docs/encoder.rst | 15 +++++++++++++-- tests/test_refs_count.py | 10 +++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/encoder.rst b/docs/encoder.rst index 50a93b2..a9cd281 100644 --- a/docs/encoder.rst +++ b/docs/encoder.rst @@ -54,8 +54,9 @@ :returns: a *JSON-serializable* value If implemented, this method is called whenever the serialization machinery finds a - Python object that it does not recognize: if possible, the method should returns a - *JSON encodable* version of the `value`, otherwise raise a :exc:`TypeError`: + Python object that it does not recognize: if possible, the method should returns either a + *JSON encodable* version of the `value` or an already encoded bytes object that will be used + verbatim, whitespace included: .. doctest:: @@ -79,3 +80,13 @@ >>> pe = PointEncoder(sort_keys=True) >>> pe(point) '{"x":1,"y":2}' + >>> class PreserializingPointEncoder(Encoder): + ... def default(self, obj): + ... if isinstance(obj, Point): + ... return f'{{"x": {obj.x}, "y": {obj.y}}}'.encode() + ... else: + ... raise ValueError('%r is not JSON serializable' % obj) + ... + >>> pe = PreserializingPointEncoder(sort_keys=True) + >>> pe(point) + '{"x": 1, "y": 2}' diff --git a/tests/test_refs_count.py b/tests/test_refs_count.py index 7e737c2..206778c 100644 --- a/tests/test_refs_count.py +++ b/tests/test_refs_count.py @@ -138,16 +138,20 @@ def string(self, string): assert (rc1 - rc0) < THRESHOLD +@pytest.mark.parametrize('value', ['Foo', b'Foo']) @pytest.mark.skipif(not hasattr(sys, 'gettotalrefcount'), reason='Non-debug Python') -def test_encoder_call_leaks(): +def test_encoder_call_leaks(value): class MyEncoder(rj.Encoder): + def __init__(self, value): + self.value = value + def default(self, obj): - return 'Foo' + return self.value class Foo: pass - encoder = MyEncoder() + encoder = MyEncoder(value) foo = Foo() rc0 = sys.gettotalrefcount() for i in range(1000): From 2fe8c70a36ddc1af8095d95476f9393802cd8e16 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Tue, 6 Mar 2018 11:48:27 +0100 Subject: [PATCH 03/12] Fix mac os build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 72e9f8d..f391732 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ env: # Note: TWINE_PASSWORD is set in Travis settings install: - - if [ $TRAVIS_OS_NAME = osx ]; then brew install python3; fi + - if [ $TRAVIS_OS_NAME = osx ]; then brew upgrade python; fi - pip3 install -r requirements-test.txt script: From 2a825eb9fe75694ea05d0a8c5a49a18066b5092f Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Thu, 22 Mar 2018 14:00:33 +0100 Subject: [PATCH 04/12] Introduce RawJSON class --- rapidjson.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++ tests/test_rawjson.py | 35 ++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 tests/test_rawjson.py diff --git a/rapidjson.cpp b/rapidjson.cpp index 40893b6..58edb83 100644 --- a/rapidjson.cpp +++ b/rapidjson.cpp @@ -415,6 +415,107 @@ inline void PutUnsafe(PyWriteStreamWrapper& stream, char c) { } +///////////// +// RawJSON // +///////////// + + +typedef struct { + PyObject_HEAD + PyObject *value; +} RawJSON; + +static void +RawJSON_dealloc(RawJSON* self) +{ + Py_XDECREF(self->value); + Py_TYPE(self)->tp_free((PyObject*)self); +} + + +static int +RawJSON_init(RawJSON *self, PyObject *args, PyObject *kwds) +{ + PyObject *value=NULL, *tmp; + + static char *kwlist[] = {"value", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "S", kwlist, + &value)) + return -1; + + if (!PyBytes_Check(value)) { + PyErr_SetString(PyExc_TypeError, "Only byte strings allowed"); + return NULL; + } + + if (value) { + tmp = self->value; + Py_INCREF(value); + self->value = value; + Py_XDECREF(tmp); + } + + return 0; +} + +static PyMemberDef RawJSON_members[] = { + {"value", T_OBJECT_EX, offsetof(RawJSON, value), 0, + "Byte string representing a serialized JSON object"}, + {NULL} /* Sentinel */ +}; + + +PyDoc_STRVAR(rawjson_doc, + "Raw (preserialized) JSON objects\n" + "\n" + "When rapidjson tries to serialize objects of this class, it will" + " use their literal `value`. For instance:\n" + ">>> rapidjson.dumps(RawJSON(b'{\"already\": \"serialized\"}'))\n" + "'{\"already\": \"serialized\"}'"); + +static PyTypeObject RawJSON_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "rapidjson.RawJSON", /* tp_name */ + sizeof(RawJSON), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)RawJSON_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + rawjson_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + RawJSON_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)RawJSON_init, /* tp_init */ + 0, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ +}; + + ///////////// // Decoder // ///////////// @@ -3695,6 +3796,12 @@ PyInit_rapidjson() Py_INCREF(&Validator_Type); PyModule_AddObject(m, "Validator", (PyObject*) &Validator_Type); + RawJSON_Type.tp_new = PyType_GenericNew; + if (PyType_Ready(&RawJSON_Type) < 0) + return NULL; + Py_INCREF(&RawJSON_Type); + PyModule_AddObject(m, "RawJSON", (PyObject *)&RawJSON_Type); + return m; error: diff --git a/tests/test_rawjson.py b/tests/test_rawjson.py new file mode 100644 index 0000000..4a3c7c7 --- /dev/null +++ b/tests/test_rawjson.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# :Project: python-rapidjson -- Tests on RawJSON +# :Author: Silvio Tomatis +# :License: MIT License +# :Copyright: © 2018 Silvio Tomatis +# :Copyright: © 2018 Lele Gaifax +# + +import pytest + +import rapidjson as rj + + +@pytest.mark.unit +def test_instantiation_positional(): + value = b'a bytes string' + raw = rj.RawJSON(value) + assert raw.value == b'a bytes string' + + +@pytest.mark.unit +def test_instantiation_named(): + value = b'a bytes string' + raw = rj.RawJSON(value=value) + assert raw.value == b'a bytes string' + + +@pytest.mark.unit +def test_only_bytes_allowed(): + with pytest.raises(TypeError): + rj.RawJSON('not a bytes string') + with pytest.raises(TypeError): + rj.RawJSON({}) + with pytest.raises(TypeError): + rj.RawJSON(None) From 113c967101e435239119035116ff2ee221950c75 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Thu, 22 Mar 2018 15:10:58 +0100 Subject: [PATCH 05/12] Use RawJSON to signify preserialized objects --- rapidjson.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/rapidjson.cpp b/rapidjson.cpp index 58edb83..eaf1f24 100644 --- a/rapidjson.cpp +++ b/rapidjson.cpp @@ -2724,28 +2724,25 @@ dumps_internal( writer->EndArray(); } + else if PyObject_TypeCheck(object, &RawJSON_Type) { + Py_ssize_t l = PyBytes_Size(((RawJSON*)object)->value); + ASSERT_VALID_SIZE(l); + const char* s = PyBytes_AsString(((RawJSON*)object)->value); + writer->RawValue(s, (SizeType) l, kStringType); + } else if (defaultFn) { PyObject* retval = PyObject_CallFunctionObjArgs(defaultFn, object, NULL); if (retval == NULL) return false; - if (PyBytes_Check(retval)) { - Py_ssize_t l = PyBytes_Size(retval); - ASSERT_VALID_SIZE(l); - const char* s = PyBytes_AsString(retval); - writer->RawValue(s, (SizeType) l, kStringType); - Py_DECREF(retval); - } - else { - if (Py_EnterRecursiveCall(" while JSONifying default function result")) { - Py_DECREF(retval); - return false; - } - bool r = RECURSE(retval); - Py_LeaveRecursiveCall(); + if (Py_EnterRecursiveCall(" while JSONifying default function result")) { Py_DECREF(retval); - if (!r) - return false; + return false; } + bool r = RECURSE(retval); + Py_LeaveRecursiveCall(); + Py_DECREF(retval); + if (!r) + return false; } else { PyErr_Format(PyExc_TypeError, "%R is not JSON serializable", object); From c373e148db7eeda254116eab5ab41568b14b6392 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Thu, 22 Mar 2018 15:32:21 +0100 Subject: [PATCH 06/12] Tests for RawJSON --- tests/test_rawjson.py | 4 ++++ tests/test_refs_count.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_rawjson.py b/tests/test_rawjson.py index 4a3c7c7..999f92f 100644 --- a/tests/test_rawjson.py +++ b/tests/test_rawjson.py @@ -33,3 +33,7 @@ def test_only_bytes_allowed(): rj.RawJSON({}) with pytest.raises(TypeError): rj.RawJSON(None) + + +def test_mix_preserialized(): + assert rj.dumps({'a': rj.RawJSON(b'{1 : 2}')}) == '{"a":{1 : 2}}' diff --git a/tests/test_refs_count.py b/tests/test_refs_count.py index 206778c..b030f90 100644 --- a/tests/test_refs_count.py +++ b/tests/test_refs_count.py @@ -76,6 +76,7 @@ def default(obj): ( plain_string, NO_OPTION, NO_OPTION ), ( bigint, NO_OPTION, NO_OPTION ), ( pi, NO_OPTION, NO_OPTION ), + ( rj.RawJSON(b' "foo" '), NO_OPTION, NO_OPTION), ( right_now, DATETIMES, DATETIMES ), ( date, DATETIMES, DATETIMES ), ( time, DATETIMES, DATETIMES ), @@ -138,7 +139,7 @@ def string(self, string): assert (rc1 - rc0) < THRESHOLD -@pytest.mark.parametrize('value', ['Foo', b'Foo']) +@pytest.mark.parametrize('value', ['Foo', rj.RawJSON(b'Foo')]) @pytest.mark.skipif(not hasattr(sys, 'gettotalrefcount'), reason='Non-debug Python') def test_encoder_call_leaks(value): class MyEncoder(rj.Encoder): From 1730b229f0b0bef2166f5f06a15640dbce34e143 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Thu, 22 Mar 2018 16:24:32 +0100 Subject: [PATCH 07/12] Document the RawJSON class --- docs/api.rst | 1 + docs/encoder.rst | 15 ++------------- docs/rawjson.rst | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 docs/rawjson.rst diff --git a/docs/api.rst b/docs/api.rst index 8cf1a77..fbe72af 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -21,6 +21,7 @@ encoder decoder validator + rawjson .. data:: __author__ diff --git a/docs/encoder.rst b/docs/encoder.rst index a9cd281..50a93b2 100644 --- a/docs/encoder.rst +++ b/docs/encoder.rst @@ -54,9 +54,8 @@ :returns: a *JSON-serializable* value If implemented, this method is called whenever the serialization machinery finds a - Python object that it does not recognize: if possible, the method should returns either a - *JSON encodable* version of the `value` or an already encoded bytes object that will be used - verbatim, whitespace included: + Python object that it does not recognize: if possible, the method should returns a + *JSON encodable* version of the `value`, otherwise raise a :exc:`TypeError`: .. doctest:: @@ -80,13 +79,3 @@ >>> pe = PointEncoder(sort_keys=True) >>> pe(point) '{"x":1,"y":2}' - >>> class PreserializingPointEncoder(Encoder): - ... def default(self, obj): - ... if isinstance(obj, Point): - ... return f'{{"x": {obj.x}, "y": {obj.y}}}'.encode() - ... else: - ... raise ValueError('%r is not JSON serializable' % obj) - ... - >>> pe = PreserializingPointEncoder(sort_keys=True) - >>> pe(point) - '{"x": 1, "y": 2}' diff --git a/docs/rawjson.rst b/docs/rawjson.rst new file mode 100644 index 0000000..a8ef1ce --- /dev/null +++ b/docs/rawjson.rst @@ -0,0 +1,27 @@ +.. -*- coding: utf-8 -*- +.. :Project: python-rapidjson -- RawJSON class documentation +.. :Author: Silvio Tomatis +.. :License: MIT License +.. :Copyright: © 2018 Silvio Tomatis +.. :Copyright: © 2018 Lele Gaifax +.. + +=============== + RawJSON class +=============== + +A possible use case is mixing preserialized objects with regular ones. + +For instance, you might want to store preserialized records in your database, +and then be able to use rapidjson to pack them in a serialized list. + +The RawJSON class serves this purpose. + +It must be instantiated with a bytestring: + + .. doctest:: + + >>> import rapidjson + >>> raw_list = rapidjson.RawJSON(b'[1, 2,3]') + >>> rapidjson.dumps({'foo': raw_list}) + '{"foo":[1, 2,3]}' From 968cc9696e5c4815813a140e96dd07594afb3170 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Thu, 22 Mar 2018 16:57:53 +0100 Subject: [PATCH 08/12] Run doctests in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f391732..b3e486b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ install: - pip3 install -r requirements-test.txt script: - - pip3 install . && pytest tests + - pip3 install . && pytest tests && cd docs; make doctest; cd - - | pip3 install cibuildwheel==0.6.0 cibuildwheel --output-dir wheelhouse From 15762d9970a2d655ada2c95a38452121a6ee2eaf Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Thu, 22 Mar 2018 17:50:41 +0100 Subject: [PATCH 09/12] Use unicode strings for RawJSON instead of byte strings --- docs/rawjson.rst | 2 +- rapidjson.cpp | 17 ++++++++++------- tests/test_rawjson.py | 12 ++++++------ tests/test_refs_count.py | 4 ++-- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/rawjson.rst b/docs/rawjson.rst index a8ef1ce..00ecb46 100644 --- a/docs/rawjson.rst +++ b/docs/rawjson.rst @@ -22,6 +22,6 @@ It must be instantiated with a bytestring: .. doctest:: >>> import rapidjson - >>> raw_list = rapidjson.RawJSON(b'[1, 2,3]') + >>> raw_list = rapidjson.RawJSON('[1, 2,3]') >>> rapidjson.dumps({'foo': raw_list}) '{"foo":[1, 2,3]}' diff --git a/rapidjson.cpp b/rapidjson.cpp index eaf1f24..ff49ccd 100644 --- a/rapidjson.cpp +++ b/rapidjson.cpp @@ -440,13 +440,13 @@ RawJSON_init(RawJSON *self, PyObject *args, PyObject *kwds) static char *kwlist[] = {"value", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, kwds, "S", kwlist, + if (! PyArg_ParseTupleAndKeywords(args, kwds, "U", kwlist, &value)) return -1; - if (!PyBytes_Check(value)) { - PyErr_SetString(PyExc_TypeError, "Only byte strings allowed"); - return NULL; + if (!PyUnicode_Check(value)) { + PyErr_SetString(PyExc_TypeError, "Only unicode strings allowed"); + return -1; } if (value) { @@ -2725,10 +2725,13 @@ dumps_internal( writer->EndArray(); } else if PyObject_TypeCheck(object, &RawJSON_Type) { - Py_ssize_t l = PyBytes_Size(((RawJSON*)object)->value); + const char* jsonStr; + Py_ssize_t l; + jsonStr = PyUnicode_AsUTF8AndSize(((RawJSON*)object)->value, &l); + if (jsonStr == NULL) + return false; ASSERT_VALID_SIZE(l); - const char* s = PyBytes_AsString(((RawJSON*)object)->value); - writer->RawValue(s, (SizeType) l, kStringType); + writer->RawValue(jsonStr, (SizeType) l, kStringType); } else if (defaultFn) { PyObject* retval = PyObject_CallFunctionObjArgs(defaultFn, object, NULL); diff --git a/tests/test_rawjson.py b/tests/test_rawjson.py index 999f92f..d495bb4 100644 --- a/tests/test_rawjson.py +++ b/tests/test_rawjson.py @@ -13,22 +13,22 @@ @pytest.mark.unit def test_instantiation_positional(): - value = b'a bytes string' + value = 'a string' raw = rj.RawJSON(value) - assert raw.value == b'a bytes string' + assert raw.value == value @pytest.mark.unit def test_instantiation_named(): - value = b'a bytes string' + value = 'a string' raw = rj.RawJSON(value=value) - assert raw.value == b'a bytes string' + assert raw.value == value @pytest.mark.unit def test_only_bytes_allowed(): with pytest.raises(TypeError): - rj.RawJSON('not a bytes string') + rj.RawJSON(b'A bytes string') with pytest.raises(TypeError): rj.RawJSON({}) with pytest.raises(TypeError): @@ -36,4 +36,4 @@ def test_only_bytes_allowed(): def test_mix_preserialized(): - assert rj.dumps({'a': rj.RawJSON(b'{1 : 2}')}) == '{"a":{1 : 2}}' + assert rj.dumps({'a': rj.RawJSON('{1 : 2}')}) == '{"a":{1 : 2}}' diff --git a/tests/test_refs_count.py b/tests/test_refs_count.py index b030f90..6f99ef9 100644 --- a/tests/test_refs_count.py +++ b/tests/test_refs_count.py @@ -76,7 +76,7 @@ def default(obj): ( plain_string, NO_OPTION, NO_OPTION ), ( bigint, NO_OPTION, NO_OPTION ), ( pi, NO_OPTION, NO_OPTION ), - ( rj.RawJSON(b' "foo" '), NO_OPTION, NO_OPTION), + ( rj.RawJSON(' "foo" '), NO_OPTION, NO_OPTION), ( right_now, DATETIMES, DATETIMES ), ( date, DATETIMES, DATETIMES ), ( time, DATETIMES, DATETIMES ), @@ -139,7 +139,7 @@ def string(self, string): assert (rc1 - rc0) < THRESHOLD -@pytest.mark.parametrize('value', ['Foo', rj.RawJSON(b'Foo')]) +@pytest.mark.parametrize('value', ['Foo', rj.RawJSON('Foo')]) @pytest.mark.skipif(not hasattr(sys, 'gettotalrefcount'), reason='Non-debug Python') def test_encoder_call_leaks(value): class MyEncoder(rj.Encoder): From ef193544994ebd95e1f166aef68c4e45afe1d888 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Thu, 22 Mar 2018 17:51:41 +0100 Subject: [PATCH 10/12] Warn RawJSON users about possibly invalid JSON output --- docs/rawjson.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/rawjson.rst b/docs/rawjson.rst index 00ecb46..934c878 100644 --- a/docs/rawjson.rst +++ b/docs/rawjson.rst @@ -25,3 +25,10 @@ It must be instantiated with a bytestring: >>> raw_list = rapidjson.RawJSON('[1, 2,3]') >>> rapidjson.dumps({'foo': raw_list}) '{"foo":[1, 2,3]}' + +Rapidjson runs no checks on the preserialized data. This means that it can +potentially output invalid JSON, if you provide it. + >>> import rapidjson + >>> raw_list = rapidjson.RawJSON('[1, ') + >>> rapidjson.dumps({'foo': raw_list}) + '{"foo":[1, }' From bfc366be407bac76039e1f0bd5d6550b2b4131c9 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Fri, 23 Mar 2018 10:41:31 +0100 Subject: [PATCH 11/12] Better RawJSON docs --- docs/rawjson.rst | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/rawjson.rst b/docs/rawjson.rst index 934c878..638f2ff 100644 --- a/docs/rawjson.rst +++ b/docs/rawjson.rst @@ -10,25 +10,35 @@ RawJSON class =============== -A possible use case is mixing preserialized objects with regular ones. +.. module:: rapidjson -For instance, you might want to store preserialized records in your database, -and then be able to use rapidjson to pack them in a serialized list. +.. testsetup:: -The RawJSON class serves this purpose. + from rapidjson import RawJSON, dumps -It must be instantiated with a bytestring: +.. class:: RawJSON(value) + + Preserialized JSON string. + + :param str value: The string that rapidjson should use verbatim when serializing this object + +Some applications might decide to store JSON-serialized objects in their database, +but might need to assemble them in a bigger JSON. + +The RawJSON class serves this purpose. When serialized, the string value provided will be used verbatim, +whitespace included. .. doctest:: - >>> import rapidjson - >>> raw_list = rapidjson.RawJSON('[1, 2,3]') - >>> rapidjson.dumps({'foo': raw_list}) + >>> raw_list = RawJSON('[1, 2,3]') + >>> dumps({'foo': raw_list}) '{"foo":[1, 2,3]}' Rapidjson runs no checks on the preserialized data. This means that it can -potentially output invalid JSON, if you provide it. - >>> import rapidjson - >>> raw_list = rapidjson.RawJSON('[1, ') - >>> rapidjson.dumps({'foo': raw_list}) +potentially output invalid JSON, if you provide it: + + .. doctest:: + + >>> raw_list = RawJSON('[1, ') + >>> dumps({'foo': raw_list}) '{"foo":[1, }' From 8fe1f1f6cb49e87b284fe1815a9ed9e06fee71e5 Mon Sep 17 00:00:00 2001 From: Silvio Tomatis Date: Fri, 23 Mar 2018 10:44:25 +0100 Subject: [PATCH 12/12] Remove reference to bytesting --- rapidjson.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rapidjson.cpp b/rapidjson.cpp index ff49ccd..74182c3 100644 --- a/rapidjson.cpp +++ b/rapidjson.cpp @@ -471,7 +471,7 @@ PyDoc_STRVAR(rawjson_doc, "\n" "When rapidjson tries to serialize objects of this class, it will" " use their literal `value`. For instance:\n" - ">>> rapidjson.dumps(RawJSON(b'{\"already\": \"serialized\"}'))\n" + ">>> rapidjson.dumps(RawJSON('{\"already\": \"serialized\"}'))\n" "'{\"already\": \"serialized\"}'"); static PyTypeObject RawJSON_Type = {