Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
qobject: Factor JSON writer out of qobject_to_json()
We have two JSON writers written in C: qobject/qjson.c provides qobject_to_json(), and migration/qjson.c provides a more low level imperative interface. They don't share code. The latter tacitly limits numbers to int64_t, and strings contents to characters that don't need escaping. Factor out qobject_to_json()'s JSON writer as qobject/json-writer.c. Straightforward, except for numbers: since the writer is to be independent of QObject, it can't use qnum_to_string(). Open-code it instead. This is actually an improvement of sorts, because it liberates qnum_to_string() from JSON's needs: its JSON-related FIXMEs move to the JSON writer, where they belong. The next commit will replace migration/qjson.c. Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20201211171152.146877-16-armbru@redhat.com>
- Loading branch information
Markus Armbruster
committed
Dec 19, 2020
1 parent
91f54d9
commit 998da0b
Showing
5 changed files
with
317 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* JSON Writer | ||
* | ||
* Copyright (c) 2020 Red Hat Inc. | ||
* | ||
* Authors: | ||
* Markus Armbruster <armbru@redhat.com> | ||
* | ||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later. | ||
* See the COPYING.LIB file in the top-level directory. | ||
* | ||
*/ | ||
|
||
#ifndef JSON_WRITER_H | ||
#define JSON_WRITER_H | ||
|
||
typedef struct JSONWriter JSONWriter; | ||
|
||
JSONWriter *json_writer_new(bool pretty); | ||
const char *json_writer_get(JSONWriter *); | ||
GString *json_writer_get_and_free(JSONWriter *); | ||
void json_writer_free(JSONWriter *); | ||
|
||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(JSONWriter, json_writer_free) | ||
|
||
void json_writer_start_object(JSONWriter *, const char *name); | ||
void json_writer_end_object(JSONWriter *); | ||
void json_writer_start_array(JSONWriter *, const char *name); | ||
void json_writer_end_array(JSONWriter *); | ||
void json_writer_bool(JSONWriter *, const char *name, bool val); | ||
void json_writer_null(JSONWriter *, const char *name); | ||
void json_writer_int64(JSONWriter *, const char *name, int64_t val); | ||
void json_writer_uint64(JSONWriter *, const char *name, uint64_t val); | ||
void json_writer_double(JSONWriter *, const char *name, double val); | ||
void json_writer_str(JSONWriter *, const char *name, const char *str); | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
/* | ||
* JSON Writer | ||
* | ||
* Copyright IBM, Corp. 2009 | ||
* Copyright (c) 2010-2020 Red Hat Inc. | ||
* | ||
* Authors: | ||
* Anthony Liguori <aliguori@us.ibm.com> | ||
* Markus Armbruster <armbru@redhat.com> | ||
* | ||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later. | ||
* See the COPYING.LIB file in the top-level directory. | ||
* | ||
*/ | ||
|
||
#include "qemu/osdep.h" | ||
#include "qapi/qmp/json-writer.h" | ||
#include "qemu/unicode.h" | ||
|
||
struct JSONWriter { | ||
bool pretty; | ||
bool need_comma; | ||
GString *contents; | ||
GByteArray *container_is_array; | ||
}; | ||
|
||
JSONWriter *json_writer_new(bool pretty) | ||
{ | ||
JSONWriter *writer = g_new(JSONWriter, 1); | ||
|
||
writer->pretty = pretty; | ||
writer->need_comma = false; | ||
writer->contents = g_string_new(NULL); | ||
writer->container_is_array = g_byte_array_new(); | ||
return writer; | ||
} | ||
|
||
const char *json_writer_get(JSONWriter *writer) | ||
{ | ||
g_assert(!writer->container_is_array->len); | ||
return writer->contents->str; | ||
} | ||
|
||
GString *json_writer_get_and_free(JSONWriter *writer) | ||
{ | ||
GString *contents = writer->contents; | ||
|
||
writer->contents = NULL; | ||
g_byte_array_free(writer->container_is_array, true); | ||
g_free(writer); | ||
return contents; | ||
} | ||
|
||
void json_writer_free(JSONWriter *writer) | ||
{ | ||
if (writer) { | ||
g_string_free(json_writer_get_and_free(writer), true); | ||
} | ||
} | ||
|
||
static void enter_container(JSONWriter *writer, bool is_array) | ||
{ | ||
unsigned depth = writer->container_is_array->len; | ||
|
||
g_byte_array_set_size(writer->container_is_array, depth + 1); | ||
writer->container_is_array->data[depth] = is_array; | ||
writer->need_comma = false; | ||
} | ||
|
||
static void leave_container(JSONWriter *writer, bool is_array) | ||
{ | ||
unsigned depth = writer->container_is_array->len; | ||
|
||
assert(depth); | ||
assert(writer->container_is_array->data[depth - 1] == is_array); | ||
g_byte_array_set_size(writer->container_is_array, depth - 1); | ||
writer->need_comma = true; | ||
} | ||
|
||
static bool in_object(JSONWriter *writer) | ||
{ | ||
unsigned depth = writer->container_is_array->len; | ||
|
||
return depth && !writer->container_is_array->data[depth - 1]; | ||
} | ||
|
||
static void pretty_newline(JSONWriter *writer) | ||
{ | ||
if (writer->pretty) { | ||
g_string_append_printf(writer->contents, "\n%*s", | ||
writer->container_is_array->len * 4, ""); | ||
} | ||
} | ||
|
||
static void pretty_newline_or_space(JSONWriter *writer) | ||
{ | ||
if (writer->pretty) { | ||
g_string_append_printf(writer->contents, "\n%*s", | ||
writer->container_is_array->len * 4, ""); | ||
} else { | ||
g_string_append_c(writer->contents, ' '); | ||
} | ||
} | ||
|
||
static void quoted_str(JSONWriter *writer, const char *str) | ||
{ | ||
const char *ptr; | ||
char *end; | ||
int cp; | ||
|
||
g_string_append_c(writer->contents, '"'); | ||
|
||
for (ptr = str; *ptr; ptr = end) { | ||
cp = mod_utf8_codepoint(ptr, 6, &end); | ||
switch (cp) { | ||
case '\"': | ||
g_string_append(writer->contents, "\\\""); | ||
break; | ||
case '\\': | ||
g_string_append(writer->contents, "\\\\"); | ||
break; | ||
case '\b': | ||
g_string_append(writer->contents, "\\b"); | ||
break; | ||
case '\f': | ||
g_string_append(writer->contents, "\\f"); | ||
break; | ||
case '\n': | ||
g_string_append(writer->contents, "\\n"); | ||
break; | ||
case '\r': | ||
g_string_append(writer->contents, "\\r"); | ||
break; | ||
case '\t': | ||
g_string_append(writer->contents, "\\t"); | ||
break; | ||
default: | ||
if (cp < 0) { | ||
cp = 0xFFFD; /* replacement character */ | ||
} | ||
if (cp > 0xFFFF) { | ||
/* beyond BMP; need a surrogate pair */ | ||
g_string_append_printf(writer->contents, "\\u%04X\\u%04X", | ||
0xD800 + ((cp - 0x10000) >> 10), | ||
0xDC00 + ((cp - 0x10000) & 0x3FF)); | ||
} else if (cp < 0x20 || cp >= 0x7F) { | ||
g_string_append_printf(writer->contents, "\\u%04X", cp); | ||
} else { | ||
g_string_append_c(writer->contents, cp); | ||
} | ||
} | ||
}; | ||
|
||
g_string_append_c(writer->contents, '"'); | ||
} | ||
|
||
static void maybe_comma_name(JSONWriter *writer, const char *name) | ||
{ | ||
if (writer->need_comma) { | ||
g_string_append_c(writer->contents, ','); | ||
pretty_newline_or_space(writer); | ||
} else { | ||
if (writer->contents->len) { | ||
pretty_newline(writer); | ||
} | ||
writer->need_comma = true; | ||
} | ||
|
||
if (in_object(writer)) { | ||
quoted_str(writer, name); | ||
g_string_append(writer->contents, ": "); | ||
} | ||
} | ||
|
||
void json_writer_start_object(JSONWriter *writer, const char *name) | ||
{ | ||
maybe_comma_name(writer, name); | ||
g_string_append_c(writer->contents, '{'); | ||
enter_container(writer, false); | ||
} | ||
|
||
void json_writer_end_object(JSONWriter *writer) | ||
{ | ||
leave_container(writer, false); | ||
pretty_newline(writer); | ||
g_string_append_c(writer->contents, '}'); | ||
} | ||
|
||
void json_writer_start_array(JSONWriter *writer, const char *name) | ||
{ | ||
maybe_comma_name(writer, name); | ||
g_string_append_c(writer->contents, '['); | ||
enter_container(writer, true); | ||
} | ||
|
||
void json_writer_end_array(JSONWriter *writer) | ||
{ | ||
leave_container(writer, true); | ||
pretty_newline(writer); | ||
g_string_append_c(writer->contents, ']'); | ||
} | ||
|
||
void json_writer_bool(JSONWriter *writer, const char *name, bool val) | ||
{ | ||
maybe_comma_name(writer, name); | ||
g_string_append(writer->contents, val ? "true" : "false"); | ||
} | ||
|
||
void json_writer_null(JSONWriter *writer, const char *name) | ||
{ | ||
maybe_comma_name(writer, name); | ||
g_string_append(writer->contents, "null"); | ||
} | ||
|
||
void json_writer_int64(JSONWriter *writer, const char *name, int64_t val) | ||
{ | ||
maybe_comma_name(writer, name); | ||
g_string_append_printf(writer->contents, "%" PRId64, val); | ||
} | ||
|
||
void json_writer_uint64(JSONWriter *writer, const char *name, uint64_t val) | ||
{ | ||
maybe_comma_name(writer, name); | ||
g_string_append_printf(writer->contents, "%" PRIu64, val); | ||
} | ||
|
||
void json_writer_double(JSONWriter *writer, const char *name, double val) | ||
{ | ||
maybe_comma_name(writer, name); | ||
|
||
/* | ||
* FIXME: g_string_append_printf() is locale dependent; but JSON | ||
* requires numbers to be formatted as if in the C locale. | ||
* Dependence on C locale is a pervasive issue in QEMU. | ||
*/ | ||
/* | ||
* FIXME: This risks printing Inf or NaN, which are not valid | ||
* JSON values. | ||
*/ | ||
g_string_append_printf(writer->contents, "%.17g", val); | ||
} | ||
|
||
void json_writer_str(JSONWriter *writer, const char *name, const char *str) | ||
{ | ||
maybe_comma_name(writer, name); | ||
quoted_str(writer, str); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
util_ss.add(files('qnull.c', 'qnum.c', 'qstring.c', 'qdict.c', 'qlist.c', 'qbool.c', | ||
'qlit.c', 'qjson.c', 'qobject.c', 'json-lexer.c', 'json-streamer.c', 'json-parser.c', | ||
util_ss.add(files('qnull.c', 'qnum.c', 'qstring.c', 'qdict.c', | ||
'qlist.c', 'qbool.c', 'qlit.c', 'qjson.c', 'qobject.c', | ||
'json-writer.c', 'json-lexer.c', 'json-streamer.c', 'json-parser.c', | ||
'block-qdict.c')) |
Oops, something went wrong.