extmod/modujson: Add support for dump/dumps separators keyword-argument.

Optionally enabled via MICROPY_PY_UJSON_SEPARATORS.  Enabled by default.

For dump, make sure mp_get_stream_raise is called after
mod_ujson_separators since CPython does it in this order (if both
separators and stream are invalid, separators will raise an exception
first).

Add separators argument in the docs as well.

Signed-off-by: Peter Züger <zueger.peter@icloud.com>
Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Peter Züger 2021-02-03 09:24:25 +01:00 committed by Damien George
parent 8616129f2e
commit ffc854f17f
7 changed files with 99 additions and 6 deletions

View File

@ -12,14 +12,20 @@ data format.
Functions Functions
--------- ---------
.. function:: dump(obj, stream) .. function:: dump(obj, stream, separators=None)
Serialise *obj* to a JSON string, writing it to the given *stream*. 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. Return *obj* represented as a JSON string.
The arguments have the same meaning as in `dump`.
.. function:: load(stream) .. function:: load(stream)
Parse the given *stream*, interpreting it as a JSON string and Parse the given *stream*, interpreting it as a JSON string and

View File

@ -34,6 +34,62 @@
#if MICROPY_PY_UJSON #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) { 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_get_stream_raise(stream, MP_STREAM_OP_WRITE);
mp_print_t print = {MP_OBJ_TO_PTR(stream), mp_stream_write_adaptor}; 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); 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 function below implements a simple non-recursive JSON parser.
// //
// The JSON specification is at http://www.ietf.org/rfc/rfc4627.txt // The JSON specification is at http://www.ietf.org/rfc/rfc4627.txt

View File

@ -1380,6 +1380,11 @@ typedef double mp_float_t;
#define MICROPY_PY_UJSON (0) #define MICROPY_PY_UJSON (0)
#endif #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 #ifndef MICROPY_PY_URE
#define MICROPY_PY_URE (0) #define MICROPY_PY_URE (0)
#endif #endif

View File

@ -52,6 +52,14 @@ typedef struct _mp_print_t {
mp_print_strn_t print_strn; mp_print_strn_t print_strn;
} mp_print_t; } 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. // All (non-debug) prints go through one of the two interfaces below.
// 1) Wrapper for platform print function, which wraps MP_PLAT_PRINT_STRN. // 1) Wrapper for platform print function, which wraps MP_PLAT_PRINT_STRN.
extern const mp_print_t mp_plat_print; extern const mp_print_t mp_plat_print;

View File

@ -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) { 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); mp_obj_dict_t *self = MP_OBJ_TO_PTR(self_in);
bool first = true; bool first = true;
const char *item_separator = ", ";
const char *key_separator = ": ";
if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) {
kind = PRINT_REPR; 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) { if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) {
mp_printf(print, "%q(", self->base.type->name); 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; mp_map_elem_t *next = NULL;
while ((next = dict_iter_next(self, &cur)) != NULL) { while ((next = dict_iter_next(self, &cur)) != NULL) {
if (!first) { if (!first) {
mp_print_str(print, ", "); mp_print_str(print, item_separator);
} }
first = false; first = false;
bool add_quote = MICROPY_PY_UJSON && kind == PRINT_JSON && !mp_obj_is_str_or_bytes(next->key); 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) { if (add_quote) {
mp_print_str(print, "\""); mp_print_str(print, "\"");
} }
mp_print_str(print, ": "); mp_print_str(print, key_separator);
mp_obj_print_helper(print, next->value, kind); mp_obj_print_helper(print, next->value, kind);
} }
mp_print_str(print, "}"); mp_print_str(print, "}");

View File

@ -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) { 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); mp_obj_list_t *o = MP_OBJ_TO_PTR(o_in);
const char *item_separator = ", ";
if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) { if (!(MICROPY_PY_UJSON && kind == PRINT_JSON)) {
kind = PRINT_REPR; kind = PRINT_REPR;
} else {
#if MICROPY_PY_UJSON_SEPARATORS
item_separator = MP_PRINT_GET_EXT(print)->item_separator;
#endif
} }
mp_print_str(print, "["); mp_print_str(print, "[");
for (size_t i = 0; i < o->len; i++) { for (size_t i = 0; i < o->len; i++) {
if (i > 0) { if (i > 0) {
mp_print_str(print, ", "); mp_print_str(print, item_separator);
} }
mp_obj_print_helper(print, o->items[i], kind); mp_obj_print_helper(print, o->items[i], kind);
} }

View File

@ -39,15 +39,19 @@
void mp_obj_tuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { 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); mp_obj_tuple_t *o = MP_OBJ_TO_PTR(o_in);
const char *item_separator = ", ";
if (MICROPY_PY_UJSON && kind == PRINT_JSON) { if (MICROPY_PY_UJSON && kind == PRINT_JSON) {
mp_print_str(print, "["); mp_print_str(print, "[");
#if MICROPY_PY_UJSON_SEPARATORS
item_separator = MP_PRINT_GET_EXT(print)->item_separator;
#endif
} else { } else {
mp_print_str(print, "("); mp_print_str(print, "(");
kind = PRINT_REPR; kind = PRINT_REPR;
} }
for (size_t i = 0; i < o->len; i++) { for (size_t i = 0; i < o->len; i++) {
if (i > 0) { if (i > 0) {
mp_print_str(print, ", "); mp_print_str(print, item_separator);
} }
mp_obj_print_helper(print, o->items[i], kind); mp_obj_print_helper(print, o->items[i], kind);
} }