@@ -283,10 +283,22 @@ PyTypeObject *type_for_conversion(PyObject *obj) {
283
283
* Recursively convert a Python object into yyjson elements.
284
284
*/
285
285
static inline yyjson_mut_val * mut_primitive_to_element (
286
- yyjson_mut_doc * doc , PyObject * obj
286
+ DocumentObject * self ,
287
+ yyjson_mut_doc * doc ,
288
+ PyObject * obj
287
289
) {
288
290
const PyTypeObject * ob_type = type_for_conversion (obj );
289
291
292
+ if (yyjson_unlikely (ob_type == NULL ) && self -> default_func != NULL ) {
293
+ PyObject * result = PyObject_CallFunctionObjArgs (self -> default_func , obj , NULL );
294
+ if (result == NULL ) {
295
+ return NULL ;
296
+ }
297
+ yyjson_mut_val * val = mut_primitive_to_element (self , doc , result );
298
+ Py_DECREF (result );
299
+ return val ;
300
+ }
301
+
290
302
if (ob_type == & PyUnicode_Type ) {
291
303
Py_ssize_t str_len ;
292
304
const char * str = PyUnicode_AsUTF8AndSize (obj , & str_len );
@@ -321,7 +333,7 @@ static inline yyjson_mut_val *mut_primitive_to_element(
321
333
yyjson_mut_val * val = yyjson_mut_arr (doc );
322
334
yyjson_mut_val * object_value = NULL ;
323
335
for (Py_ssize_t i = 0 ; i < PyList_GET_SIZE (obj ); i ++ ) {
324
- object_value = mut_primitive_to_element (doc , PyList_GET_ITEM (obj , i ));
336
+ object_value = mut_primitive_to_element (self , doc , PyList_GET_ITEM (obj , i ));
325
337
326
338
if (yyjson_unlikely (object_value == NULL )) {
327
339
return NULL ;
@@ -339,7 +351,7 @@ static inline yyjson_mut_val *mut_primitive_to_element(
339
351
while (PyDict_Next (obj , & i , & key , & value )) {
340
352
Py_ssize_t str_len ;
341
353
const char * str = PyUnicode_AsUTF8AndSize (key , & str_len );
342
- object_value = mut_primitive_to_element (doc , value );
354
+ object_value = mut_primitive_to_element (self , doc , value );
343
355
if (yyjson_unlikely (object_value == NULL )) {
344
356
return NULL ;
345
357
}
@@ -359,11 +371,9 @@ static inline yyjson_mut_val *mut_primitive_to_element(
359
371
} else if (obj == Py_None ) {
360
372
return yyjson_mut_null (doc );
361
373
} else {
362
- PyErr_SetString (
363
- PyExc_TypeError ,
364
- // TODO: We can provide a much better error here. Also add support
365
- // for a default hook.
366
- "Tried to serialize an object we don't know how to handle."
374
+ PyErr_Format (PyExc_TypeError ,
375
+ "Object of type '%s' is not JSON serializable" ,
376
+ Py_TYPE (obj )-> tp_name
367
377
);
368
378
return NULL ;
369
379
}
@@ -372,6 +382,7 @@ static inline yyjson_mut_val *mut_primitive_to_element(
372
382
static void Document_dealloc (DocumentObject * self ) {
373
383
if (self -> i_doc != NULL ) yyjson_doc_free (self -> i_doc );
374
384
if (self -> m_doc != NULL ) yyjson_mut_doc_free (self -> m_doc );
385
+ Py_XDECREF (self -> default_func );
375
386
Py_TYPE (self )-> tp_free ((PyObject * )self );
376
387
}
377
388
@@ -426,20 +437,33 @@ PyDoc_STRVAR(
426
437
":param content: The initial content of the document.\n"
427
438
":type content: ``str``, ``bytes``, ``Path``, ``dict``, ``list``\n"
428
439
":param flags: Flags that modify the document parsing behaviour.\n"
429
- ":type flags: :class:`ReaderFlags`, optional"
440
+ ":type flags: :class:`ReaderFlags`, optional\n"
441
+ ":param default: A function called to convert objects that are not\n"
442
+ " JSON serializable. Should return a JSON serializable version\n"
443
+ " of the object or raise a TypeError.\n"
444
+ ":type default: callable, optional"
430
445
);
431
446
static int Document_init (DocumentObject * self , PyObject * args , PyObject * kwds ) {
432
- static char * kwlist [] = {"content" , "flags" , NULL };
447
+ static char * kwlist [] = {"content" , "flags" , "default" , NULL };
433
448
PyObject * content ;
449
+ PyObject * default_func = NULL ;
434
450
yyjson_read_err err ;
435
451
yyjson_read_flag r_flag = 0 ;
436
452
437
453
if (!PyArg_ParseTupleAndKeywords (
438
- args , kwds , "O|$I " , kwlist , & content , & r_flag
454
+ args , kwds , "O|$IO " , kwlist , & content , & r_flag , & default_func
439
455
)) {
440
456
return -1 ;
441
457
}
442
458
459
+ if (default_func && !PyCallable_Check (default_func )) {
460
+ PyErr_SetString (PyExc_TypeError , "default must be callable" );
461
+ return -1 ;
462
+ }
463
+
464
+ self -> default_func = default_func ;
465
+ Py_XINCREF (default_func );
466
+
443
467
if (yyjson_unlikely (pathlib == NULL )) {
444
468
pathlib = PyImport_ImportModule ("pathlib" );
445
469
if (yyjson_unlikely (pathlib == NULL )) {
@@ -523,7 +547,7 @@ static int Document_init(DocumentObject *self, PyObject *args, PyObject *kwds) {
523
547
return -1 ;
524
548
}
525
549
526
- yyjson_mut_val * val = mut_primitive_to_element (self -> m_doc , content );
550
+ yyjson_mut_val * val = mut_primitive_to_element (self , self -> m_doc , content );
527
551
528
552
if (val == NULL ) {
529
553
return -1 ;
0 commit comments