diff --git a/include/qapi/qmp/qjson.h b/include/qapi/qmp/qjson.h
index 02b1f2c..aa8ddd7 100644
--- a/include/qapi/qmp/qjson.h
+++ b/include/qapi/qmp/qjson.h
@@ -24,4 +24,6 @@ QObject *qobject_from_jsonv(const char *string, va_list *ap) GCC_FMT_ATTR(1, 0);
 QString *qobject_to_json(const QObject *obj);
 QString *qobject_to_json_pretty(const QObject *obj);

+int qstring_append_json_string(QString *qstring, const char *str);
+
 #endif /* QJSON_H */
diff --git a/migration/qjson.c b/migration/qjson.c
index f345904..741f9e6 100644
--- a/migration/qjson.c
+++ b/migration/qjson.c
@@ -26,6 +26,7 @@
 #include "qemu/osdep.h"
 #include "qapi/qmp/qstring.h"
 #include "migration/qjson.h"
+#include "qapi/qmp/qjson.h"

 struct QJSON {
     QString *str;
@@ -42,9 +43,8 @@ static void json_emit_element(QJSON *json, const char *name)
     }

     if (name) {
-        qstring_append(json->str, "\"");
-        qstring_append(json->str, name);
-        qstring_append(json->str, "\" : ");
+        qstring_append_json_string(json->str, name);
+        qstring_append(json->str, " : ");
     }
 }

@@ -83,9 +83,7 @@ void json_prop_int(QJSON *json, const char *name, int64_t val)
 void json_prop_str(QJSON *json, const char *name, const char *str)
 {
     json_emit_element(json, name);
-    qstring_append_chr(json->str, '"');
-    qstring_append(json->str, str);
-    qstring_append_chr(json->str, '"');
+    qstring_append_json_string(json->str, str);
 }

 const char *qjson_get_str(QJSON *json)
diff --git a/qobject/qjson.c b/qobject/qjson.c
index 906a8fc..6810726 100644
--- a/qobject/qjson.c
+++ b/qobject/qjson.c
@@ -82,7 +82,6 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent);
 static void to_json_dict_iter(const char *key, QObject *obj, void *opaque)
 {
     ToJsonIterState *s = opaque;
-    QString *qkey;
     int j;

     if (s->count) {
@@ -95,9 +94,7 @@ static void to_json_dict_iter(const char *key, QObject *obj, void *opaque)
             qstring_append(s->str, "    ");
     }

-    qkey = qstring_from_str(key);
-    to_json(QOBJECT(qkey), s->str, s->pretty, s->indent);
-    QDECREF(qkey);
+    qstring_append_json_string(s->str, key);

     qstring_append(s->str, ": ");
     to_json(obj, s->str, s->pretty, s->indent);
@@ -139,58 +136,9 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent)
     }
     case QTYPE_QSTRING: {
         QString *val = qobject_to_qstring(obj);
-        const char *ptr;
-        int cp;
-        char buf[16];
-        char *end;
-
-        ptr = qstring_get_str(val);
-        qstring_append(str, "\"");
-
-        for (; *ptr; ptr = end) {
-            cp = mod_utf8_codepoint(ptr, 6, &end);
-            switch (cp) {
-            case '\"':
-                qstring_append(str, "\\\"");
-                break;
-            case '\\':
-                qstring_append(str, "\\\\");
-                break;
-            case '\b':
-                qstring_append(str, "\\b");
-                break;
-            case '\f':
-                qstring_append(str, "\\f");
-                break;
-            case '\n':
-                qstring_append(str, "\\n");
-                break;
-            case '\r':
-                qstring_append(str, "\\r");
-                break;
-            case '\t':
-                qstring_append(str, "\\t");
-                break;
-            default:
-                if (cp < 0) {
-                    cp = 0xFFFD; /* replacement character */
-                }
-                if (cp > 0xFFFF) {
-                    /* beyond BMP; need a surrogate pair */
-                    snprintf(buf, sizeof(buf), "\\u%04X\\u%04X",
-                             0xD800 + ((cp - 0x10000) >> 10),
-                             0xDC00 + ((cp - 0x10000) & 0x3FF));
-                } else if (cp < 0x20 || cp >= 0x7F) {
-                    snprintf(buf, sizeof(buf), "\\u%04X", cp);
-                } else {
-                    buf[0] = cp;
-                    buf[1] = 0;
-                }
-                qstring_append(str, buf);
-            }
-        };
-
-        qstring_append(str, "\"");
+        /* FIXME: no way inform user if we replaced invalid UTF-8
+         * sequences to avoid invalid JSON */
+        qstring_append_json_string(str, qstring_get_str(val));
         break;
     }
     case QTYPE_QDICT: {
@@ -290,3 +238,69 @@ QString *qobject_to_json_pretty(const QObject *obj)

     return str;
 }
+
+/**
+ * Append a JSON string to @qstring that encodes the C string @str.
+ *
+ * @str is in UTF-8.  The JSON string is enclosed in double quotes and
+ * has funny characters escaped.  Invalid UTF-8 sequences are replaced
+ * by (properly escaped) U+0xFFFD replacement characters. Return -1 if
+ * invalid sequences were replaced, else 0.
+ */
+int qstring_append_json_string(QString *qstring, const char *str)
+{
+    const char *ptr;
+    int cp;
+    char buf[16];
+    char *end;
+    int result = 0;
+
+    qstring_append(qstring, "\"");
+
+    for (ptr = str; *ptr; ptr = end) {
+        cp = mod_utf8_codepoint(ptr, 6, &end);
+        switch (cp) {
+        case '\"':
+            qstring_append(qstring, "\\\"");
+            break;
+        case '\\':
+            qstring_append(qstring, "\\\\");
+            break;
+        case '\b':
+            qstring_append(qstring, "\\b");
+            break;
+        case '\f':
+            qstring_append(qstring, "\\f");
+            break;
+        case '\n':
+            qstring_append(qstring, "\\n");
+            break;
+        case '\r':
+            qstring_append(qstring, "\\r");
+            break;
+        case '\t':
+            qstring_append(qstring, "\\t");
+            break;
+        default:
+            if (cp < 0) {
+                cp = 0xFFFD; /* replacement character */
+                result = -1;
+            }
+            if (cp > 0xFFFF) {
+                /* beyond BMP; need a surrogate pair */
+                snprintf(buf, sizeof(buf), "\\u%04X\\u%04X",
+                         0xD800 + ((cp - 0x10000) >> 10),
+                         0xDC00 + ((cp - 0x10000) & 0x3FF));
+            } else if (cp < 0x20 || cp >= 0x7F) {
+                snprintf(buf, sizeof(buf), "\\u%04X", cp);
+            } else {
+                buf[0] = cp;
+                buf[1] = 0;
+            }
+            qstring_append(qstring, buf);
+        }
+    };
+
+    qstring_append(qstring, "\"");
+    return result;
+}
