1
0
Fork 0
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:
Mitar 2014-12-19 19:29:13 +01:00
parent c9744834ab
commit a8f0f0f101
4 changed files with 144 additions and 8 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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: