diff --git a/Documentation/ref/ovs-appctl.8.rst b/Documentation/ref/ovs-appctl.8.rst
index 3ce02e984..9619c1226 100644
--- a/Documentation/ref/ovs-appctl.8.rst
+++ b/Documentation/ref/ovs-appctl.8.rst
@@ -8,6 +8,7 @@ Synopsis
 ``ovs-appctl``
 [``--target=``<target> | ``-t`` <target>]
 [``--timeout=``<secs> | ``-T`` <secs>]
+[``--format=``<format> | ``-f`` <format>]
 <command> [<arg>...]
 
 ``ovs-appctl --help``
@@ -67,6 +68,17 @@ In normal use only a single option is accepted:
   runtime to approximately <secs> seconds.  If the timeout expires,
   ``ovs-appctl`` exits with a ``SIGALRM`` signal.
 
+* ``-f <format>`` or ``--format=<format>``
+
+  Tells ``ovs-appctl`` which output format to use. By default, or with a
+  <format> of ``text``, ``ovs-appctl`` will print plain-text for humans.
+  When <format> is ``json``, ``ovs-appctl`` will return a JSON document.
+  When ``json`` is requested, but a command has not implemented JSON
+  output, the plain-text output will be wrapped in a provisional JSON
+  document with the following structure:
+
+  ``{"reply-format":"plain","reply":"$PLAIN_TEXT_HERE"}``
+
 Common Commands
 ===============
 
diff --git a/NEWS b/NEWS
index b92cec532..3c52e5ec1 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,8 @@
 Post-v3.3.0
 --------------------
+   - ovs-appctl:
+     * Added new option [-f|--format] to choose the output format, e.g. 'json'
+       or 'text' (by default).
    - Userspace datapath:
      * Conntrack now supports 'random' flag for selecting ports in a range
        while natting and 'persistent' flag for selection of the IP address
diff --git a/lib/unixctl.c b/lib/unixctl.c
index 103357ee9..c430eac0b 100644
--- a/lib/unixctl.c
+++ b/lib/unixctl.c
@@ -17,11 +17,13 @@
 #include <config.h>
 #include "unixctl.h"
 #include <errno.h>
+#include <getopt.h>
 #include <unistd.h>
+#include "command-line.h"
 #include "coverage.h"
 #include "dirs.h"
 #include "openvswitch/dynamic-string.h"
-#include "openvswitch/json.h"
+#include "json.h"
 #include "jsonrpc.h"
 #include "openvswitch/list.h"
 #include "openvswitch/poll-loop.h"
@@ -50,6 +52,8 @@ struct unixctl_conn {
     /* Only one request can be in progress at a time.  While the request is
      * being processed, 'request_id' is populated, otherwise it is null. */
     struct json *request_id;   /* ID of the currently active request. */
+
+    enum unixctl_output_fmt fmt; /* Output format of current connection. */
 };
 
 /* Server for control connection. */
@@ -63,6 +67,42 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 
 static struct shash commands = SHASH_INITIALIZER(&commands);
 
+const char *
+unixctl_output_fmt_to_string(enum unixctl_output_fmt fmt)
+{
+    switch (fmt) {
+    case UNIXCTL_OUTPUT_FMT_TEXT:
+        return "text";
+
+    case UNIXCTL_OUTPUT_FMT_JSON:
+        return "json";
+
+    default:
+        return NULL;
+    }
+}
+
+struct json *
+unixctl_output_fmt_to_json(enum unixctl_output_fmt fmt)
+{
+    const char *string = unixctl_output_fmt_to_string(fmt);
+    return string ? json_string_create(string) : NULL;
+}
+
+bool
+unixctl_output_fmt_from_string(const char *string,
+                               enum unixctl_output_fmt *fmt)
+{
+    if (!strcmp(string, "text")) {
+        *fmt = UNIXCTL_OUTPUT_FMT_TEXT;
+    } else if (!strcmp(string, "json")) {
+        *fmt = UNIXCTL_OUTPUT_FMT_JSON;
+    } else {
+        return false;
+    }
+    return true;
+}
+
 static void
 unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED,
                       const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
@@ -94,6 +134,48 @@ unixctl_version(struct unixctl_conn *conn, int argc OVS_UNUSED,
     unixctl_command_reply(conn, ovs_get_program_version());
 }
 
+static void
+unixctl_set_options(struct unixctl_conn *conn, int argc, const char *argv[],
+                    void *aux OVS_UNUSED)
+{
+    struct ovs_cmdl_parsed_option *parsed_options = NULL;
+    size_t n_parsed_options;
+    char *error = NULL;
+
+    static const struct option options[] = {
+        {"format", required_argument, NULL, 'f'},
+        {NULL, 0, NULL, 0},
+    };
+
+    error = ovs_cmdl_parse_all(argc--, (char **) (argv++), options,
+                               &parsed_options, &n_parsed_options);
+    if (error) {
+        goto error;
+    }
+
+    for (size_t i = 0; i < n_parsed_options; i++) {
+        struct ovs_cmdl_parsed_option *parsed_option = parsed_options + i;
+        if (parsed_option->o == (options + 0)) {
+            /* format */
+            if (!unixctl_output_fmt_from_string(parsed_option->arg,
+                                                &conn->fmt)) {
+                error = xasprintf("option format has invalid value %s",
+                                  parsed_option->arg);
+                goto error;
+            }
+        } else  {
+            OVS_NOT_REACHED();
+        }
+    }
+    unixctl_command_reply(conn, NULL);
+    free(parsed_options);
+    return;
+error:
+    unixctl_command_reply_error(conn, error);
+    free(error);
+    free(parsed_options);
+}
+
 /* Registers a unixctl command with the given 'name'.  'usage' describes the
  * arguments to the command; it is used only for presentation to the user in
  * "list-commands" output.  (If 'usage' is NULL, then the command is hidden.)
@@ -128,36 +210,41 @@ unixctl_command_register(const char *name, const char *usage,
     shash_add(&commands, name, command);
 }
 
+enum unixctl_output_fmt
+unixctl_command_get_output_format(struct unixctl_conn *conn)
+{
+    return conn->fmt;
+}
+
+void
+unixctl_command_set_output_format(struct unixctl_conn *conn,
+                                  enum unixctl_output_fmt fmt)
+{
+    conn->fmt = fmt;
+}
+
+/* Takes ownership of the 'body'. */
 static void
 unixctl_command_reply__(struct unixctl_conn *conn,
-                        bool success, const char *body)
+                        bool success, struct json *body)
 {
-    struct json *body_json;
     struct jsonrpc_msg *reply;
 
     COVERAGE_INC(unixctl_replied);
     ovs_assert(conn->request_id);
 
-    if (!body) {
-        body = "";
-    }
-
-    if (body[0] && body[strlen(body) - 1] != '\n') {
-        body_json = json_string_create_nocopy(xasprintf("%s\n", body));
-    } else {
-        body_json = json_string_create(body);
-    }
-
     if (success) {
-        reply = jsonrpc_create_reply(body_json, conn->request_id);
+        reply = jsonrpc_create_reply(body, conn->request_id);
     } else {
-        reply = jsonrpc_create_error(body_json, conn->request_id);
+        reply = jsonrpc_create_error(body, conn->request_id);
     }
 
     if (VLOG_IS_DBG_ENABLED()) {
         char *id = json_to_string(conn->request_id, 0);
+        char *msg = json_to_string(body, JSSF_SORT);
         VLOG_DBG("replying with %s, id=%s: \"%s\"",
-                 success ? "success" : "error", id, body);
+                 success ? "success" : "error", id, msg);
+        free(msg);
         free(id);
     }
 
@@ -169,23 +256,52 @@ unixctl_command_reply__(struct unixctl_conn *conn,
 }
 
 /* Replies to the active unixctl connection 'conn'.  'result' is sent to the
- * client indicating the command was processed successfully.  Only one call to
- * unixctl_command_reply() or unixctl_command_reply_error() may be made per
- * request. */
+ * client indicating the command was processed successfully.  'result' should
+ * be plain-text; use unixctl_command_reply_json() to return a JSON document
+ * when JSON output has been requested.  Only one call to
+ * unixctl_command_reply*() functions may be made per request. */
 void
 unixctl_command_reply(struct unixctl_conn *conn, const char *result)
 {
-    unixctl_command_reply__(conn, true, result);
+    struct json * json_result = json_string_create(result ? result : "");
+
+    if (conn->fmt == UNIXCTL_OUTPUT_FMT_JSON) {
+        /* Wrap plain-text reply in provisional JSON document when JSON output
+         * has been requested. */
+        struct json *json_reply = json_object_create();
+
+        json_object_put_string(json_reply, "reply-format", "plain");
+        json_object_put(json_reply, "reply", json_result);
+
+        json_result = json_reply;
+    }
+
+    unixctl_command_reply__(conn, true, json_result);
+}
+
+/* Replies to the active unixctl connection 'conn'.  'body' is sent to the
+ * client indicating the command was processed successfully.  Use this function
+ * when JSON output has been requested; otherwise use unixctl_command_reply()
+ * for plain-text output.  Only one call to unixctl_command_reply*() functions
+ * may be made per request.
+ *
+ * Takes ownership of the 'body'. */
+void
+unixctl_command_reply_json(struct unixctl_conn *conn, struct json *body)
+{
+    ovs_assert(conn->fmt == UNIXCTL_OUTPUT_FMT_JSON);
+    unixctl_command_reply__(conn, true, body);
 }
 
 /* Replies to the active unixctl connection 'conn'. 'error' is sent to the
- * client indicating an error occurred processing the command.  Only one call to
- * unixctl_command_reply() or unixctl_command_reply_error() may be made per
- * request. */
+ * client indicating an error occurred processing the command.  'error' should
+ * be plain-text.  Only one call to unixctl_command_reply*() functions may be
+ * made per request. */
 void
 unixctl_command_reply_error(struct unixctl_conn *conn, const char *error)
 {
-    unixctl_command_reply__(conn, false, error);
+    unixctl_command_reply__(conn, false,
+                            json_string_create(error ? error : ""));
 }
 
 /* Creates a unixctl server listening on 'path', which for POSIX may be:
@@ -250,6 +366,8 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp)
     unixctl_command_register("list-commands", "", 0, 0, unixctl_list_commands,
                              NULL);
     unixctl_command_register("version", "", 0, 0, unixctl_version, NULL);
+    unixctl_command_register("set-options", "[--format text|json]", 0, 2,
+                             unixctl_set_options, NULL);
 
     struct unixctl_server *server = xmalloc(sizeof *server);
     server->listener = listener;
@@ -381,6 +499,7 @@ unixctl_server_run(struct unixctl_server *server)
             struct unixctl_conn *conn = xzalloc(sizeof *conn);
             ovs_list_push_back(&server->conns, &conn->node);
             conn->rpc = jsonrpc_open(stream);
+            conn->fmt = UNIXCTL_OUTPUT_FMT_TEXT;
         } else if (error == EAGAIN) {
             break;
         } else {
@@ -483,10 +602,10 @@ unixctl_client_create(const char *path, struct jsonrpc **client)
  * '*err' if not NULL. */
 int
 unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
-                        char *argv[], char **result, char **err)
+                        char *argv[], struct json **result, struct json **err)
 {
-    struct jsonrpc_msg *request, *reply;
     struct json **json_args, *params;
+    struct jsonrpc_msg *request, *reply;
     int error, i;
 
     *result = NULL;
@@ -506,25 +625,11 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
         return error;
     }
 
-    if (reply->error) {
-        if (reply->error->type == JSON_STRING) {
-            *err = xstrdup(json_string(reply->error));
-        } else {
-            VLOG_WARN("%s: unexpected error type in JSON RPC reply: %s",
-                      jsonrpc_get_name(client),
-                      json_type_to_string(reply->error->type));
-            error = EINVAL;
-        }
-    } else if (reply->result) {
-        if (reply->result->type == JSON_STRING) {
-            *result = xstrdup(json_string(reply->result));
-        } else {
-            VLOG_WARN("%s: unexpected result type in JSON rpc reply: %s",
-                      jsonrpc_get_name(client),
-                      json_type_to_string(reply->result->type));
-            error = EINVAL;
-        }
-    }
+    ovs_assert(!reply->result || !reply->error);
+    *result = reply->result;
+    reply->result = NULL;
+    *err = reply->error;
+    reply->error = NULL;
 
     jsonrpc_msg_destroy(reply);
     return error;
diff --git a/lib/unixctl.h b/lib/unixctl.h
index 4562dbc49..61b31cb3a 100644
--- a/lib/unixctl.h
+++ b/lib/unixctl.h
@@ -17,10 +17,22 @@
 #ifndef UNIXCTL_H
 #define UNIXCTL_H 1
 
+#include <stdbool.h>
+
 #ifdef  __cplusplus
 extern "C" {
 #endif
 
+struct json;
+enum unixctl_output_fmt {
+    UNIXCTL_OUTPUT_FMT_TEXT = 1 << 0,
+    UNIXCTL_OUTPUT_FMT_JSON = 1 << 1
+};
+
+const char *unixctl_output_fmt_to_string(enum unixctl_output_fmt);
+struct json *unixctl_output_fmt_to_json(enum unixctl_output_fmt);
+bool unixctl_output_fmt_from_string(const char *, enum unixctl_output_fmt *);
+
 /* Server for Unix domain socket control connection. */
 struct unixctl_server;
 int unixctl_server_create(const char *path, struct unixctl_server **);
@@ -36,7 +48,7 @@ int unixctl_client_create(const char *path, struct jsonrpc **client);
 int unixctl_client_transact(struct jsonrpc *client,
                             const char *command,
                             int argc, char *argv[],
-                            char **result, char **error);
+                            struct json **result, struct json **error);
 
 /* Command registration. */
 struct unixctl_conn;
@@ -45,8 +57,14 @@ typedef void unixctl_cb_func(struct unixctl_conn *,
 void unixctl_command_register(const char *name, const char *usage,
                               int min_args, int max_args,
                               unixctl_cb_func *cb, void *aux);
+enum unixctl_output_fmt
+unixctl_command_get_output_format(struct unixctl_conn *);
+void unixctl_command_set_output_format(struct unixctl_conn *,
+                                       enum unixctl_output_fmt);
 void unixctl_command_reply_error(struct unixctl_conn *, const char *error);
 void unixctl_command_reply(struct unixctl_conn *, const char *body);
+void unixctl_command_reply_json(struct unixctl_conn *,
+                                struct json *body);
 
 #ifdef  __cplusplus
 }
diff --git a/lib/util.c b/lib/util.c
index 5c31d983a..87d5a4a90 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -619,11 +619,11 @@ ovs_set_program_name(const char *argv0, const char *version)
 
     free(program_version);
     if (!strcmp(version, VERSION)) {
-        program_version = xasprintf("%s (Open vSwitch) "VERSION"\n",
+        program_version = xasprintf("%s (Open vSwitch) "VERSION,
                                     program_name);
     } else {
         program_version = xasprintf("%s %s\n"
-                                    "Open vSwitch Library "VERSION"\n",
+                                    "Open vSwitch Library "VERSION,
                                     program_name, version);
     }
 }
@@ -760,7 +760,7 @@ ovs_get_program_name(void)
 void
 ovs_print_version(uint8_t min_ofp, uint8_t max_ofp)
 {
-    printf("%s", program_version);
+    printf("%s\n", program_version);
     if (min_ofp || max_ofp) {
         printf("OpenFlow versions %#x:%#x\n", min_ofp, max_ofp);
     }
diff --git a/python/ovs/unixctl/server.py b/python/ovs/unixctl/server.py
index b9cb52fad..d24a7092c 100644
--- a/python/ovs/unixctl/server.py
+++ b/python/ovs/unixctl/server.py
@@ -87,9 +87,6 @@ class UnixctlConnection(object):
         if body is None:
             body = ""
 
-        if body and not body.endswith("\n"):
-            body += "\n"
-
         if success:
             reply = Message.create_reply(body, self._request_id)
         else:
diff --git a/tests/appctl.py b/tests/appctl.py
index b85b364fa..e5cc28138 100644
--- a/tests/appctl.py
+++ b/tests/appctl.py
@@ -63,11 +63,16 @@ def main():
         ovs.util.ovs_fatal(err_no, "%s: transaction error" % target)
     elif error is not None:
         sys.stderr.write(error)
+        if error and not error.endswith("\n"):
+            sys.stderr.write("\n")
+
         ovs.util.ovs_error(0, "%s: server returned an error" % target)
         sys.exit(2)
     else:
         assert result is not None
         sys.stdout.write(result)
+        if result and not result.endswith("\n"):
+            sys.stdout.write("\n")
 
 
 if __name__ == '__main__':
diff --git a/tests/ovs-vswitchd.at b/tests/ovs-vswitchd.at
index 977b2eba1..1ae7fcc32 100644
--- a/tests/ovs-vswitchd.at
+++ b/tests/ovs-vswitchd.at
@@ -265,3 +265,14 @@ OFPT_FEATURES_REPLY: dpid:$orig_dpid
 
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([ovs-vswitchd version])
+OVS_VSWITCHD_START
+
+AT_CHECK([ovs-appctl version], [0], [ignore])
+ovs_version=$(ovs-appctl version)
+
+AT_CHECK_UNQUOTED([ovs-appctl --format json version], [0], [dnl
+{"reply":"$ovs_version","reply-format":"plain"}])
+
+AT_CLEANUP
diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c
index ba0c172e6..a359a14f1 100644
--- a/utilities/ovs-appctl.c
+++ b/utilities/ovs-appctl.c
@@ -26,57 +26,103 @@
 #include "daemon.h"
 #include "dirs.h"
 #include "openvswitch/dynamic-string.h"
+#include "json.h"
 #include "jsonrpc.h"
 #include "process.h"
 #include "timeval.h"
+#include "svec.h"
 #include "unixctl.h"
 #include "util.h"
 #include "openvswitch/vlog.h"
 
 static void usage(void);
-static const char *parse_command_line(int argc, char *argv[]);
+
+/* Parsed command line args. */
+struct cmdl_args {
+    enum unixctl_output_fmt format;
+    char *target;
+};
+
+static struct cmdl_args *cmdl_args_create(void);
+static struct cmdl_args *parse_command_line(int argc, char *argv[]);
 static struct jsonrpc *connect_to_target(const char *target);
+static char * reply_to_string(struct json *reply, enum unixctl_output_fmt fmt);
 
 int
 main(int argc, char *argv[])
 {
-    char *cmd_result, *cmd_error;
+    struct svec opt_argv = SVEC_EMPTY_INITIALIZER;
+    struct json *cmd_result, *cmd_error;
     struct jsonrpc *client;
+    struct cmdl_args *args;
     char *cmd, **cmd_argv;
-    const char *target;
+    char *msg = NULL;
     int cmd_argc;
     int error;
 
     set_program_name(argv[0]);
 
     /* Parse command line and connect to target. */
-    target = parse_command_line(argc, argv);
-    client = connect_to_target(target);
+    args = parse_command_line(argc, argv);
+    client = connect_to_target(args->target);
 
-    /* Transact request and process reply. */
+    /* Transact options request (if required) and process reply. */
+    if (args->format != UNIXCTL_OUTPUT_FMT_TEXT) {
+        svec_add(&opt_argv, "--format");
+        svec_add(&opt_argv, unixctl_output_fmt_to_string(args->format));
+    }
+    svec_terminate(&opt_argv);
+
+    if (!svec_is_empty(&opt_argv)) {
+        error = unixctl_client_transact(client, "set-options",
+                                        opt_argv.n, opt_argv.names,
+                                        &cmd_result, &cmd_error);
+
+        if (error) {
+            ovs_fatal(error, "%s: transaction error", args->target);
+        }
+
+        if (cmd_error) {
+            jsonrpc_close(client);
+            msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT);
+            fputs(msg, stderr);
+            ovs_error(0, "%s: server returned an error", args->target);
+            exit(2);
+        }
+
+        json_destroy(cmd_result);
+        json_destroy(cmd_error);
+    }
+    svec_destroy(&opt_argv);
+
+    /* Transact command request and process reply. */
     cmd = argv[optind++];
     cmd_argc = argc - optind;
     cmd_argv = cmd_argc ? argv + optind : NULL;
     error = unixctl_client_transact(client, cmd, cmd_argc, cmd_argv,
                                     &cmd_result, &cmd_error);
     if (error) {
-        ovs_fatal(error, "%s: transaction error", target);
+        ovs_fatal(error, "%s: transaction error", args->target);
     }
 
     if (cmd_error) {
         jsonrpc_close(client);
-        fputs(cmd_error, stderr);
-        ovs_error(0, "%s: server returned an error", target);
+        msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT);
+        fputs(msg, stderr);
+        ovs_error(0, "%s: server returned an error", args->target);
         exit(2);
     } else if (cmd_result) {
-        fputs(cmd_result, stdout);
+        msg = reply_to_string(cmd_result, args->format);
+        fputs(msg, stdout);
     } else {
         OVS_NOT_REACHED();
     }
 
     jsonrpc_close(client);
-    free(cmd_result);
-    free(cmd_error);
+    json_destroy(cmd_result);
+    json_destroy(cmd_error);
+    free(args);
+    free(msg);
     return 0;
 }
 
@@ -101,13 +147,26 @@ Common commands:\n\
   vlog/reopen        Make the program reopen its log file\n\
 Other options:\n\
   --timeout=SECS     wait at most SECS seconds for a response\n\
+  -f, --format=FMT   Output format. One of: 'json', or 'text'\n\
+                     (default: text)\n\
   -h, --help         Print this helpful information\n\
   -V, --version      Display ovs-appctl version information\n",
            program_name, program_name);
     exit(EXIT_SUCCESS);
 }
 
-static const char *
+static struct cmdl_args *
+cmdl_args_create(void)
+{
+    struct cmdl_args *args = xmalloc(sizeof *args);
+
+    args->format = UNIXCTL_OUTPUT_FMT_TEXT;
+    args->target = NULL;
+
+    return args;
+}
+
+static struct cmdl_args *
 parse_command_line(int argc, char *argv[])
 {
     enum {
@@ -117,6 +176,7 @@ parse_command_line(int argc, char *argv[])
     static const struct option long_options[] = {
         {"target", required_argument, NULL, 't'},
         {"execute", no_argument, NULL, 'e'},
+        {"format", required_argument, NULL, 'f'},
         {"help", no_argument, NULL, 'h'},
         {"option", no_argument, NULL, 'o'},
         {"version", no_argument, NULL, 'V'},
@@ -126,11 +186,10 @@ parse_command_line(int argc, char *argv[])
     };
     char *short_options_ = ovs_cmdl_long_options_to_short_options(long_options);
     char *short_options = xasprintf("+%s", short_options_);
-    const char *target;
-    int e_options;
+    struct cmdl_args *args = cmdl_args_create();
     unsigned int timeout = 0;
+    int e_options;
 
-    target = NULL;
     e_options = 0;
     for (;;) {
         int option;
@@ -141,10 +200,10 @@ parse_command_line(int argc, char *argv[])
         }
         switch (option) {
         case 't':
-            if (target) {
+            if (args->target) {
                 ovs_fatal(0, "-t or --target may be specified only once");
             }
-            target = optarg;
+            args->target = optarg;
             break;
 
         case 'e':
@@ -157,6 +216,12 @@ parse_command_line(int argc, char *argv[])
             }
             break;
 
+        case 'f':
+            if (!unixctl_output_fmt_from_string(optarg, &args->format)) {
+                ovs_fatal(0, "value %s on -f or --format is invalid", optarg);
+            }
+            break;
+
         case 'h':
             usage();
             break;
@@ -194,7 +259,10 @@ parse_command_line(int argc, char *argv[])
                   "(use --help for help)");
     }
 
-    return target ? target : "ovs-vswitchd";
+    if (!args->target) {
+        args->target = "ovs-vswitchd";
+    }
+    return args;
 }
 
 static struct jsonrpc *
@@ -236,3 +304,27 @@ connect_to_target(const char *target)
     return client;
 }
 
+/* The caller is responsible for freeing the returned string, with free(), when
+ * it is no longer needed. */
+static char *
+reply_to_string(struct json *reply, enum unixctl_output_fmt fmt)
+{
+    ovs_assert(reply);
+
+    if (fmt == UNIXCTL_OUTPUT_FMT_TEXT && reply->type == JSON_STRING) {
+        const char * body = json_string(reply);
+
+        if (body[0] && body[strlen(body) - 1] != '\n') {
+            return xasprintf("%s\n", body);
+        } else {
+            return xstrdup(body);
+        }
+    }
+
+    if (fmt != UNIXCTL_OUTPUT_FMT_JSON) {
+        ovs_error(0, "Unexpected reply type in JSON rpc reply: %s",
+                  json_type_to_string(reply->type));
+    }
+
+    return json_to_string(reply, JSSF_SORT);
+}
