diff --git a/docs/library/ujson.rst b/docs/library/ujson.rst index 5668eb21a8..65ed1867e6 100644 --- a/docs/library/ujson.rst +++ b/docs/library/ujson.rst @@ -12,14 +12,20 @@ data format. Functions --------- -.. function:: dump(obj, stream) +.. function:: dump(obj, stream, separators=None) Serialise *obj* to a JSON string, writing it to the given *stream*. -.. function:: dumps(obj) + If specified, separators should be an ``(item_separator, key_separator)`` + tuple. The default is ``(', ', ': ')``. To get the most compact JSON + representation, you should specify ``(',', ':')`` to eliminate whitespace. + +.. function:: dumps(obj, separators=None) Return *obj* represented as a JSON string. + The arguments have the same meaning as in `dump`. + .. function:: load(stream) Parse the given *stream*, interpreting it as a JSON string and diff --git a/extmod/modujson.c b/extmod/modujson.c index 8dff673580..e5bfb0d966 100644 --- a/extmod/modujson.c +++ b/extmod/modujson.c @@ -34,6 +34,62 @@ #if MICROPY_PY_UJSON +#if MICROPY_PY_UJSON_SEPARATORS + +enum { + DUMP_MODE_TO_STRING = 1, + DUMP_MODE_TO_STREAM = 2, +}; + +STATIC mp_obj_t mod_ujson_dump_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, unsigned int mode) { + enum { ARG_separators }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_separators, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - mode, pos_args + mode, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_print_ext_t print_ext; + + if (args[ARG_separators].u_obj == mp_const_none) { + print_ext.item_separator = ", "; + print_ext.key_separator = ": "; + } else { + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[ARG_separators].u_obj, 2, &items); + print_ext.item_separator = mp_obj_str_get_str(items[0]); + print_ext.key_separator = mp_obj_str_get_str(items[1]); + } + + if (mode == DUMP_MODE_TO_STRING) { + // dumps(obj) + vstr_t vstr; + vstr_init_print(&vstr, 8, &print_ext.base); + mp_obj_print_helper(&print_ext.base, pos_args[0], PRINT_JSON); + return mp_obj_new_str_from_vstr(&mp_type_str, &vstr); + } else { + // dump(obj, stream) + print_ext.base.data = MP_OBJ_TO_PTR(pos_args[1]); + print_ext.base.print_strn = mp_stream_write_adaptor; + mp_get_stream_raise(pos_args[1], MP_STREAM_OP_WRITE); + mp_obj_print_helper(&print_ext.base, pos_args[0], PRINT_JSON); + return mp_const_none; + } +} + +STATIC mp_obj_t mod_ujson_dump(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return mod_ujson_dump_helper(n_args, pos_args, kw_args, DUMP_MODE_TO_STREAM); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_ujson_dump_obj, 2, mod_ujson_dump); + +STATIC mp_obj_t mod_ujson_dumps(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return mod_ujson_dump_helper(n_args, pos_args, kw_args, DUMP_MODE_TO_STRING); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_ujson_dumps_obj, 1, mod_ujson_dumps); + +#else + STATIC mp_obj_t mod_ujson_dump(mp_obj_t obj, mp_obj_t stream) { mp_get_stream_raise(stream, MP_STREAM_OP_WRITE); mp_print_t print = {MP_OBJ_TO_PTR(stream), mp_stream_write_adaptor}; @@ -51,6 +107,8 @@ STATIC mp_obj_t mod_ujson_dumps(mp_obj_t obj) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_ujson_dumps_obj, mod_ujson_dumps); +#endif + // The function below implements a simple non-recursive JSON parser. // // The JSON specification is at http://www.ietf.org/rfc/rfc4627.txt diff --git a/py/mpconfig.h b/py/mpconfig.h index f67e11cd4b..a91c39b018 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1380,6 +1380,11 @@ typedef double mp_float_t; #define MICROPY_PY_UJSON (0) #endif +// Whether to support the "separators" argument to dump, dumps +#ifndef MICROPY_PY_UJSON_SEPARATORS +#define MICROPY_PY_UJSON_SEPARATORS (1) +#endif + #ifndef MICROPY_PY_URE #define MICROPY_PY_URE (0) #endif diff --git a/py/mpprint.h b/py/mpprint.h index aef9015deb..0dff9a770a 100644 --- a/py/mpprint.h +++ b/py/mpprint.h @@ -52,6 +52,14 @@ typedef struct _mp_print_t { mp_print_strn_t print_strn; } mp_print_t; +typedef struct _mp_print_ext_t { + mp_print_t base; + const char *item_separator; + const char *key_separator; +}mp_print_ext_t; + +#define MP_PRINT_GET_EXT(print) ((mp_print_ext_t *)print) + // All (non-debug) prints go through one of the two interfaces below. // 1) Wrapper for platform print function, which wraps MP_PLAT_PRINT_STRN. extern const mp_print_t mp_plat_print; diff --git a/py/objdict.c b/py/objdict.c index 63e5381c66..ed4376aa4f 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -69,8 +69,15 @@ STATIC mp_map_elem_t *dict_iter_next(mp_obj_dict_t *dict, size_t *cur) { STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in); bool first = true; + const char *item_separator = ", "; + const char *key_separator = ": "; if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { kind = PRINT_REPR; + } else { + #if MICROPY_PY_UJSON_SEPARATORS + item_separator = MP_PRINT_GET_EXT(print)->item_separator; + key_separator = MP_PRINT_GET_EXT(print)->key_separator; + #endif } if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) { mp_printf(print, "%q(", self->base.type->name); @@ -80,7 +87,7 @@ STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_ mp_map_elem_t *next = NULL; while ((next = dict_iter_next(self, &cur)) != NULL) { if (!first) { - mp_print_str(print, ", "); + mp_print_str(print, item_separator); } first = false; bool add_quote = MICROPY_PY_UJSON && kind == PRINT_JSON && !mp_obj_is_str_or_bytes(next->key); @@ -91,7 +98,7 @@ STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_ if (add_quote) { mp_print_str(print, "\""); } - mp_print_str(print, ": "); + mp_print_str(print, key_separator); mp_obj_print_helper(print, next->value, kind); } mp_print_str(print, "}"); diff --git a/py/objlist.c b/py/objlist.c index 8c989facc0..f431e273df 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -44,13 +44,18 @@ STATIC mp_obj_t list_pop(size_t n_args, const mp_obj_t *args); STATIC void list_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_list_t *o = MP_OBJ_TO_PTR(o_in); + const char *item_separator = ", "; if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { kind = PRINT_REPR; + } else { + #if MICROPY_PY_UJSON_SEPARATORS + item_separator = MP_PRINT_GET_EXT(print)->item_separator; + #endif } mp_print_str(print, "["); for (size_t i = 0; i < o->len; i++) { if (i > 0) { - mp_print_str(print, ", "); + mp_print_str(print, item_separator); } mp_obj_print_helper(print, o->items[i], kind); } diff --git a/py/objtuple.c b/py/objtuple.c index 07a560ac08..67d7bc356f 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -39,15 +39,19 @@ void mp_obj_tuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { mp_obj_tuple_t *o = MP_OBJ_TO_PTR(o_in); + const char *item_separator = ", "; if (MICROPY_PY_UJSON && kind == PRINT_JSON) { mp_print_str(print, "["); + #if MICROPY_PY_UJSON_SEPARATORS + item_separator = MP_PRINT_GET_EXT(print)->item_separator; + #endif } else { mp_print_str(print, "("); kind = PRINT_REPR; } for (size_t i = 0; i < o->len; i++) { if (i > 0) { - mp_print_str(print, ", "); + mp_print_str(print, item_separator); } mp_obj_print_helper(print, o->items[i], kind); }