mirror of
https://github.com/ultrajson/ultrajson.git
synced 2024-05-29 23:16:07 +02:00
If an object has a __json__ method, use it when encoding.
It should return a raw JSON string which will be directly included in the resulting JSON when encoding.
This commit is contained in:
parent
c9744834ab
commit
a8f0f0f101
|
@ -154,6 +154,7 @@ enum JSTYPES
|
|||
JT_ULONG, // (JSUINT64 (unsigned 64-bit))
|
||||
JT_DOUBLE, // (double)
|
||||
JT_UTF8, // (char 8-bit)
|
||||
JT_RAW, // (raw char 8-bit)
|
||||
JT_ARRAY, // Array structure
|
||||
JT_OBJECT, // Key/Value structure
|
||||
JT_INVALID, // Internal, do not return nor expect
|
||||
|
|
|
@ -890,6 +890,28 @@ void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t cbName)
|
|||
}
|
||||
|
||||
Buffer_AppendCharUnchecked (enc, '\"');
|
||||
break;
|
||||
}
|
||||
|
||||
case JT_RAW:
|
||||
{
|
||||
value = enc->getStringValue(obj, &tc, &szlen);
|
||||
if(!value)
|
||||
{
|
||||
SetError(obj, enc, "utf-8 encoding error");
|
||||
return;
|
||||
}
|
||||
|
||||
Buffer_Reserve(enc, RESERVE_STRING(szlen));
|
||||
if (enc->errorMsg)
|
||||
{
|
||||
enc->endTypeContext(obj, &tc);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(enc->offset, value, szlen);
|
||||
enc->offset += szlen;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ typedef struct __TypeContext
|
|||
|
||||
union
|
||||
{
|
||||
PyObject *rawJSONValue;
|
||||
JSINT64 longValue;
|
||||
JSUINT64 unsignedLongValue;
|
||||
};
|
||||
|
@ -157,6 +158,17 @@ static void *PyUnicodeToUTF8(JSOBJ _obj, JSONTypeContext *tc, void *outValue, si
|
|||
return PyString_AS_STRING(newObj);
|
||||
}
|
||||
|
||||
static void *PyRawJSONToUTF8(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen)
|
||||
{
|
||||
PyObject *obj = GET_TC(tc)->rawJSONValue;
|
||||
if (PyUnicode_Check(obj)) {
|
||||
return PyUnicodeToUTF8(obj, tc, outValue, _outLen);
|
||||
}
|
||||
else {
|
||||
return PyStringToUTF8(obj, tc, outValue, _outLen);
|
||||
}
|
||||
}
|
||||
|
||||
static void *PyDateTimeToINT64(JSOBJ _obj, JSONTypeContext *tc, void *outValue, size_t *_outLen)
|
||||
{
|
||||
PyObject *obj = (PyObject *) _obj;
|
||||
|
@ -496,7 +508,7 @@ void SetupDictIter(PyObject *dictObj, TypeContext *pc)
|
|||
|
||||
void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc)
|
||||
{
|
||||
PyObject *obj, *exc, *toDictFunc, *iter;
|
||||
PyObject *obj, *exc, *iter;
|
||||
TypeContext *pc;
|
||||
PRINTMARK();
|
||||
if (!_obj) {
|
||||
|
@ -523,6 +535,7 @@ void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc)
|
|||
pc->index = 0;
|
||||
pc->size = 0;
|
||||
pc->longValue = 0;
|
||||
pc->rawJSONValue = NULL;
|
||||
|
||||
if (PyIter_Check(obj))
|
||||
{
|
||||
|
@ -579,7 +592,7 @@ void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc)
|
|||
return;
|
||||
}
|
||||
else
|
||||
if (PyString_Check(obj))
|
||||
if (PyString_Check(obj) && !PyObject_HasAttrString(obj, "__json__"))
|
||||
{
|
||||
PRINTMARK();
|
||||
pc->PyTypeToJSON = PyStringToUTF8; tc->type = JT_UTF8;
|
||||
|
@ -673,10 +686,9 @@ ISITERABLE:
|
|||
}
|
||||
*/
|
||||
|
||||
toDictFunc = PyObject_GetAttrString(obj, "toDict");
|
||||
|
||||
if (toDictFunc)
|
||||
if (PyObject_HasAttrString(obj, "toDict"))
|
||||
{
|
||||
PyObject* toDictFunc = PyObject_GetAttrString(obj, "toDict");
|
||||
PyObject* tuple = PyTuple_New(0);
|
||||
PyObject* toDictResult = PyObject_Call(toDictFunc, tuple, NULL);
|
||||
Py_DECREF(tuple);
|
||||
|
@ -684,9 +696,7 @@ ISITERABLE:
|
|||
|
||||
if (toDictResult == NULL)
|
||||
{
|
||||
PyErr_Clear();
|
||||
tc->type = JT_NULL;
|
||||
return;
|
||||
goto INVALID;
|
||||
}
|
||||
|
||||
if (!PyDict_Check(toDictResult))
|
||||
|
@ -701,6 +711,39 @@ ISITERABLE:
|
|||
SetupDictIter(toDictResult, pc);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if (PyObject_HasAttrString(obj, "__json__"))
|
||||
{
|
||||
PyObject* toJSONFunc = PyObject_GetAttrString(obj, "__json__");
|
||||
PyObject* tuple = PyTuple_New(0);
|
||||
PyObject* toJSONResult = PyObject_Call(toJSONFunc, tuple, NULL);
|
||||
Py_DECREF(tuple);
|
||||
Py_DECREF(toJSONFunc);
|
||||
|
||||
if (toJSONResult == NULL)
|
||||
{
|
||||
goto INVALID;
|
||||
}
|
||||
|
||||
if (PyErr_Occurred())
|
||||
{
|
||||
Py_DECREF(toJSONResult);
|
||||
goto INVALID;
|
||||
}
|
||||
|
||||
if (!PyString_Check(toJSONResult) && !PyUnicode_Check(toJSONResult))
|
||||
{
|
||||
Py_DECREF(toJSONResult);
|
||||
PyErr_Format (PyExc_TypeError, "expected string");
|
||||
goto INVALID;
|
||||
}
|
||||
|
||||
PRINTMARK();
|
||||
pc->PyTypeToJSON = PyRawJSONToUTF8;
|
||||
tc->type = JT_RAW;
|
||||
GET_TC(tc)->rawJSONValue = toJSONResult;
|
||||
return;
|
||||
}
|
||||
|
||||
PRINTMARK();
|
||||
PyErr_Clear();
|
||||
|
|
|
@ -823,18 +823,88 @@ class UltraJSONTests(unittest.TestCase):
|
|||
input = quote + (base * 1024 * 1024 * 2) + quote
|
||||
output = ujson.decode(input)
|
||||
|
||||
def test_object_default(self):
|
||||
# An object without toDict or __json__ defined should be serialized
|
||||
# as an empty dict.
|
||||
class ObjectTest:
|
||||
pass
|
||||
|
||||
output = ujson.encode(ObjectTest())
|
||||
dec = ujson.decode(output)
|
||||
self.assertEquals(dec, {})
|
||||
|
||||
def test_toDict(self):
|
||||
d = {u"key": 31337}
|
||||
|
||||
class DictTest:
|
||||
def toDict(self):
|
||||
return d
|
||||
def __json__(self):
|
||||
return '"json defined"' # Fallback and shouldn't be called.
|
||||
|
||||
o = DictTest()
|
||||
output = ujson.encode(o)
|
||||
dec = ujson.decode(output)
|
||||
self.assertEqual(dec, d)
|
||||
|
||||
def test_object_with_json(self):
|
||||
# If __json__ returns a string, then that string
|
||||
# will be used as a raw JSON snippet in the object.
|
||||
output_text = 'this is the correct output'
|
||||
class JSONTest:
|
||||
def __json__(self):
|
||||
return '"' + output_text + '"'
|
||||
|
||||
d = {u'key': JSONTest()}
|
||||
output = ujson.encode(d)
|
||||
dec = ujson.decode(output)
|
||||
self.assertEquals(dec, {u'key': output_text})
|
||||
|
||||
def test_object_with_json_unicode(self):
|
||||
# If __json__ returns a string, then that string
|
||||
# will be used as a raw JSON snippet in the object.
|
||||
output_text = u'this is the correct output'
|
||||
class JSONTest:
|
||||
def __json__(self):
|
||||
return u'"' + output_text + u'"'
|
||||
|
||||
d = {u'key': JSONTest()}
|
||||
output = ujson.encode(d)
|
||||
dec = ujson.decode(output)
|
||||
self.assertEquals(dec, {u'key': output_text})
|
||||
|
||||
def test_object_with_complex_json(self):
|
||||
# If __json__ returns a string, then that string
|
||||
# will be used as a raw JSON snippet in the object.
|
||||
obj = {u'foo': [u'bar', u'baz']}
|
||||
class JSONTest:
|
||||
def __json__(self):
|
||||
return ujson.encode(obj)
|
||||
|
||||
d = {u'key': JSONTest()}
|
||||
output = ujson.encode(d)
|
||||
dec = ujson.decode(output)
|
||||
self.assertEquals(dec, {u'key': obj})
|
||||
|
||||
def test_object_with_json_type_error(self):
|
||||
# __json__ must return a string, otherwise it should raise an error.
|
||||
for return_value in (None, 1234, 12.34, True, {}):
|
||||
class JSONTest:
|
||||
def __json__(self):
|
||||
return return_value
|
||||
|
||||
d = {u'key': JSONTest()}
|
||||
self.assertRaises(TypeError, ujson.encode, d)
|
||||
|
||||
def test_object_with_json_attribute_error(self):
|
||||
# If __json__ raises an error, make sure python actually raises it.
|
||||
class JSONTest:
|
||||
def __json__(self):
|
||||
raise AttributeError
|
||||
|
||||
d = {u'key': JSONTest()}
|
||||
self.assertRaises(AttributeError, ujson.encode, d)
|
||||
|
||||
def test_decodeArrayTrailingCommaFail(self):
|
||||
input = "[31337,]"
|
||||
try:
|
||||
|
|
Loading…
Reference in New Issue