diff --git a/steam-runtime-tools/glib-backports-internal.h b/steam-runtime-tools/glib-backports-internal.h
index 805f2ef099a1584579a12e3c3a835173556968ad..21420ba506f09b06b15b202bef4c082da32f7aa9 100644
--- a/steam-runtime-tools/glib-backports-internal.h
+++ b/steam-runtime-tools/glib-backports-internal.h
@@ -114,3 +114,9 @@ gpointer *my_g_hash_table_get_keys_as_array (GHashTable *hash,
 #define G_DBUS_METHOD_INVOCATION_HANDLED TRUE
 #define G_DBUS_METHOD_INVOCATION_UNHANDLED FALSE
 #endif
+
+#if !GLIB_CHECK_VERSION(2, 52, 0)
+#define g_utf8_make_valid(s,l) my_g_utf8_make_valid (s, l)
+gchar *my_g_utf8_make_valid (const gchar *str,
+                             gssize len);
+#endif
diff --git a/steam-runtime-tools/glib-backports.c b/steam-runtime-tools/glib-backports.c
index 2d637a3e6aef11cd6313693daf7289b49272f635..8b2a5103f2b4b3a02f43046af7cba8d651db3cb1 100644
--- a/steam-runtime-tools/glib-backports.c
+++ b/steam-runtime-tools/glib-backports.c
@@ -510,3 +510,50 @@ my_g_hash_table_get_keys_as_array (GHashTable *hash,
   return g_ptr_array_free (arr, FALSE);
 }
 #endif
+
+#if !GLIB_CHECK_VERSION(2, 52, 0)
+gchar *
+my_g_utf8_make_valid (const gchar *str,
+                      gssize       len)
+{
+  GString *string;
+  const gchar *remainder, *invalid;
+  gsize remaining_bytes, valid_bytes;
+
+  g_return_val_if_fail (str != NULL, NULL);
+
+  if (len < 0)
+    len = strlen (str);
+
+  string = NULL;
+  remainder = str;
+  remaining_bytes = len;
+
+  while (remaining_bytes != 0)
+    {
+      if (g_utf8_validate (remainder, remaining_bytes, &invalid))
+        break;
+      valid_bytes = invalid - remainder;
+
+      if (string == NULL)
+        string = g_string_sized_new (remaining_bytes);
+
+      g_string_append_len (string, remainder, valid_bytes);
+      /* append U+FFFD REPLACEMENT CHARACTER */
+      g_string_append (string, "\357\277\275");
+
+      remaining_bytes -= valid_bytes + 1;
+      remainder = invalid + 1;
+    }
+
+  if (string == NULL)
+    return g_strndup (str, len);
+
+  g_string_append_len (string, remainder, remaining_bytes);
+  g_string_append_c (string, '\0');
+
+  g_assert (g_utf8_validate (string->str, -1, NULL));
+
+  return g_string_free (string, FALSE);
+}
+#endif
diff --git a/steam-runtime-tools/json-utils-internal.h b/steam-runtime-tools/json-utils-internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..318290e1b4d7d82f7df4ce8c2bc8445257922225
--- /dev/null
+++ b/steam-runtime-tools/json-utils-internal.h
@@ -0,0 +1,48 @@
+/*<private_header>*/
+/*
+ * Copyright © 2019-2020 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <json-glib/json-glib.h>
+
+#include <steam-runtime-tools/macros.h>
+
+_SRT_PRIVATE_EXPORT
+guint srt_get_flags_from_json_array (GType flags_type,
+                                     JsonObject *json_obj,
+                                     const gchar *array_member,
+                                     guint flag_if_unknown);
+
+gchar ** _srt_json_object_dup_strv_member (JsonObject *json_obj,
+                                           const gchar *array_member,
+                                           const gchar *placeholder);
+
+gchar *_srt_json_object_dup_array_of_lines_member (JsonObject *json_obj,
+                                                   const gchar *array_member);
diff --git a/steam-runtime-tools/json-utils.c b/steam-runtime-tools/json-utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..10dfbc7f04b318812130cce618377bf2246236a7
--- /dev/null
+++ b/steam-runtime-tools/json-utils.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright © 2019-2020 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "steam-runtime-tools/json-utils-internal.h"
+
+#include "steam-runtime-tools/glib-backports-internal.h"
+#include "steam-runtime-tools/utils-internal.h"
+
+/**
+ * srt_get_flags_from_json_array:
+ * @flags_type: The type of the flag
+ * @json_obj: (not nullable): A JSON Object used to search for
+ *  @array_member property
+ * @array_member: (not nullable): The JSON member to look up
+ * @flag_if_unknown: flag to use in case of parsing error
+ *
+ * Get the flags from a given JSON object array member.
+ * If @json_obj doesn't have the provided @member, or it is malformed, the
+ * @flag_if_unknown will be returned.
+ * If the parsed JSON array_member has some elements that we can't parse,
+ * @flag_if_unknown will be added to the returned flags.
+ *
+ * Returns: the found flags from the provided @json_obj
+ */
+guint
+srt_get_flags_from_json_array (GType flags_type,
+                               JsonObject *json_obj,
+                               const gchar *array_member,
+                               guint flag_if_unknown)
+{
+  JsonArray *array;
+  guint ret = flag_if_unknown;
+
+  g_return_val_if_fail (G_TYPE_IS_FLAGS (flags_type), 0);
+  g_return_val_if_fail (json_obj != NULL, 0);
+  g_return_val_if_fail (array_member != NULL, 0);
+
+  if (json_object_has_member (json_obj, array_member))
+    {
+      array = json_object_get_array_member (json_obj, array_member);
+
+      if (array == NULL)
+        goto out;
+
+      /* We reset the value out because we found the member we were looking for */
+      ret = 0;
+
+      for (guint j = 0; j < json_array_get_length (array); j++)
+        {
+          const gchar *issue_string = json_array_get_string_element (array, j);
+          if (!srt_add_flag_from_nick (flags_type, issue_string, &ret, NULL))
+            ret |= flag_if_unknown;
+        }
+    }
+
+out:
+  return ret;
+}
+
+/**
+ * _srt_json_object_dup_strv_member:
+ * @json_obj: (not nullable): A JSON Object used to search for
+ *  @array_member property
+ * @array_member: (not nullable): The JSON member to look up
+ * @placeholder: (nullable): If an item in the array is not a string,
+ *  substitute this non-%NULL value; or if %NULL, behave as though it
+ *  was not present
+ *
+ * Returns: (transfer full) (array zero-terminated=1) (element-type utf8) (nullable):
+ *  A string array from the given @json_obj, or %NULL if it doesn't have a
+ *  property @array_member
+ */
+gchar **
+_srt_json_object_dup_strv_member (JsonObject *json_obj,
+                                  const gchar *array_member,
+                                  const gchar *placeholder)
+{
+  JsonArray *array;
+  JsonNode *arr_node;
+  guint length;
+  gchar **ret = NULL;
+
+  g_return_val_if_fail (json_obj != NULL, NULL);
+  g_return_val_if_fail (array_member != NULL, NULL);
+
+  arr_node = json_object_get_member (json_obj, array_member);
+
+  if (arr_node != NULL && JSON_NODE_HOLDS_ARRAY (arr_node))
+    {
+      guint j = 0;
+
+      array = json_node_get_array (arr_node);
+
+      if (array == NULL)
+        return ret;
+
+      length = json_array_get_length (array);
+      ret = g_new0 (gchar *, length + 1);
+
+      for (guint i = 0; i < length; i++)
+        {
+          JsonNode *node = json_array_get_element (array, i);
+          const gchar *element = json_node_get_string (node);
+
+          if (element == NULL)
+            element = placeholder;
+
+          if (element == NULL)
+            continue;
+
+          ret[j++] = g_strdup (element);
+        }
+
+      g_assert (j <= length);
+      ret[j] = NULL;
+    }
+
+  return ret;
+}
+
+/**
+ * _srt_json_object_dup_array_of_lines_member:
+ * @json_obj: (not nullable): A JSON Object used to search for
+ *  @array_member property
+ * @array_member: (not nullable): The JSON member to look up
+ *
+ * If @json_obj has a member named @array_member and it is an array
+ * of strings, concatenate the strings (adding a trailing newline to
+ * each one if not already present) and return them. Otherwise,
+ * return %NULL.
+ *
+ * Returns: (transfer full) (element-type utf8) (nullable):
+ *  A string, or %NULL if not found. Free with g_free().
+ */
+gchar *
+_srt_json_object_dup_array_of_lines_member (JsonObject *json_obj,
+                                            const gchar *array_member)
+{
+  JsonArray *array;
+  JsonNode *arr_node;
+  guint length;
+  g_autoptr(GString) ret = g_string_new ("");
+
+  g_return_val_if_fail (json_obj != NULL, NULL);
+  g_return_val_if_fail (array_member != NULL, NULL);
+
+  arr_node = json_object_get_member (json_obj, array_member);
+
+  if (arr_node == NULL || !JSON_NODE_HOLDS_ARRAY (arr_node))
+    return NULL;
+
+  array = json_node_get_array (arr_node);
+
+  if (array == NULL)
+    return NULL;
+
+  length = json_array_get_length (array);
+
+  for (guint i = 0; i < length; i++)
+    {
+      JsonNode *node = json_array_get_element (array, i);
+      const gchar *element = json_node_get_string (node);
+
+      if (element != NULL)
+        g_string_append (ret, element);
+
+      if (element == NULL || element[strlen (element) - 1] != '\n')
+        g_string_append_c (ret, '\n');
+    }
+
+  return g_string_free (g_steal_pointer (&ret), FALSE);
+}
diff --git a/steam-runtime-tools/library.c b/steam-runtime-tools/library.c
index a09c181490e903051aeafd4d0912a6cba93852f7..e77694aaff7af4aab8d51e0f19da38ecf4487c57 100644
--- a/steam-runtime-tools/library.c
+++ b/steam-runtime-tools/library.c
@@ -29,6 +29,7 @@
 #include "steam-runtime-tools/enums.h"
 #include "steam-runtime-tools/glib-backports-internal.h"
 #include "steam-runtime-tools/json-glib-backports-internal.h"
+#include "steam-runtime-tools/json-utils-internal.h"
 #include "steam-runtime-tools/library-internal.h"
 #include "steam-runtime-tools/utils.h"
 #include "steam-runtime-tools/utils-internal.h"
diff --git a/steam-runtime-tools/locale.c b/steam-runtime-tools/locale.c
index 8e46d9eb61027042d46011e3837f1637380492f6..4bc7249d0669c81761a2ab1b923717e0f584ab17 100644
--- a/steam-runtime-tools/locale.c
+++ b/steam-runtime-tools/locale.c
@@ -29,6 +29,7 @@
 #include "steam-runtime-tools/enums.h"
 #include "steam-runtime-tools/glib-backports-internal.h"
 #include "steam-runtime-tools/json-glib-backports-internal.h"
+#include "steam-runtime-tools/json-utils-internal.h"
 #include "steam-runtime-tools/locale-internal.h"
 #include "steam-runtime-tools/utils-internal.h"
 
diff --git a/steam-runtime-tools/meson.build b/steam-runtime-tools/meson.build
index 964a56fce530d7dc528baeb04ee8a934469729fc..6c0acce1c1f5ecc6f5a60ad4595b13942f88d6c4 100644
--- a/steam-runtime-tools/meson.build
+++ b/steam-runtime-tools/meson.build
@@ -34,6 +34,8 @@ libsteamrt_sources = [
     'graphics.c',
     'json-glib-backports.c',
     'json-glib-backports-internal.h',
+    'json-utils.c',
+    'json-utils-internal.h',
     'library-internal.h',
     'library.c',
     'locale-internal.h',
diff --git a/steam-runtime-tools/runtime.c b/steam-runtime-tools/runtime.c
index be908f2e3b7a297be6a10177b91558669614e04e..0cdd35405a250b3e1c1321af98316ad371b5a829 100644
--- a/steam-runtime-tools/runtime.c
+++ b/steam-runtime-tools/runtime.c
@@ -37,6 +37,7 @@
 #include <glib/gstdio.h>
 
 #include "steam-runtime-tools/glib-backports-internal.h"
+#include "steam-runtime-tools/json-utils-internal.h"
 #include "steam-runtime-tools/utils.h"
 
 /**
diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c
index fef92aba2b1cf023f4c2a6f6b03b8fcc5269b4be..2928a68e0b7b264bd7a5578218fb7b6b99715d3b 100644
--- a/steam-runtime-tools/system-info.c
+++ b/steam-runtime-tools/system-info.c
@@ -36,6 +36,7 @@
 #include "steam-runtime-tools/desktop-entry-internal.h"
 #include "steam-runtime-tools/graphics.h"
 #include "steam-runtime-tools/graphics-internal.h"
+#include "steam-runtime-tools/json-utils-internal.h"
 #include "steam-runtime-tools/library-internal.h"
 #include "steam-runtime-tools/locale-internal.h"
 #include "steam-runtime-tools/os-internal.h"
@@ -1348,9 +1349,13 @@ _srt_system_info_get_pinned_libs_from_report (JsonObject *json_obj,
     {
       json_pinned_obj = json_object_get_object_member (json_obj, which);
 
-      pinned_list = _srt_json_array_to_strv (json_pinned_obj, "list");
+      pinned_list = _srt_json_object_dup_strv_member (json_pinned_obj,
+                                                      "list",
+                                                      "<invalid>");
 
-      *messages = _srt_json_array_to_strv (json_pinned_obj, "messages");
+      *messages = _srt_json_object_dup_strv_member (json_pinned_obj,
+                                                    "messages",
+                                                    "<invalid>");
     }
 
   return pinned_list;
@@ -3231,7 +3236,9 @@ srt_system_info_list_driver_environment (SrtSystemInfo *self)
 static gchar **
 _srt_system_info_driver_environment_from_report (JsonObject *json_obj)
 {
-  return _srt_json_array_to_strv (json_obj, "driver_environment");
+  return _srt_json_object_dup_strv_member (json_obj,
+                                           "driver_environment",
+                                           "<invalid>");
 }
 
 typedef struct
diff --git a/steam-runtime-tools/utils-internal.h b/steam-runtime-tools/utils-internal.h
index 3609b4b888b1a59be2734f4fda56ea2232f134e5..3a7f0fba5871441d952de90dd25b7b23d188cc4d 100644
--- a/steam-runtime-tools/utils-internal.h
+++ b/steam-runtime-tools/utils-internal.h
@@ -30,8 +30,6 @@
 
 #include <glib.h>
 
-#include <json-glib/json-glib.h>
-
 #include <steam-runtime-tools/macros.h>
 
 typedef enum
@@ -73,12 +71,6 @@ gboolean srt_add_flag_from_nick (GType flags_type,
                                  guint *value_out,
                                  GError **error);
 
-_SRT_PRIVATE_EXPORT
-guint srt_get_flags_from_json_array (GType flags_type,
-                                     JsonObject *json_obj,
-                                     const gchar *array_member,
-                                     guint flag_if_unknown);
-
 G_GNUC_INTERNAL void _srt_child_setup_unblock_signals (gpointer ignored);
 
 _SRT_PRIVATE_EXPORT
@@ -87,9 +79,6 @@ void _srt_unblock_signals (void);
 G_GNUC_INTERNAL int _srt_indirect_strcmp0 (gconstpointer left,
                                            gconstpointer right);
 
-gchar ** _srt_json_array_to_strv (JsonObject *json_obj,
-                                  const gchar *array_member);
-
 _SRT_PRIVATE_EXPORT
 gboolean _srt_rm_rf (const char *directory);
 
diff --git a/steam-runtime-tools/utils.c b/steam-runtime-tools/utils.c
index a52f07947597aeb20efa87b3dc3fa024715be6a9..074a8ad3b19daec307893830f3e7938f097554f2 100644
--- a/steam-runtime-tools/utils.c
+++ b/steam-runtime-tools/utils.c
@@ -573,57 +573,6 @@ srt_add_flag_from_nick (GType flags_type,
   return result;
 }
 
-/**
- * srt_get_flags_from_json_array:
- * @flags_type: The type of the flag
- * @json_obj: (not nullable): A JSON Object used to search for
- *  @array_member property
- * @array_member: (not nullable): The JSON member to look up
- * @flag_if_unknown: flag to use in case of parsing error
- *
- * Get the flags from a given JSON object array member.
- * If @json_obj doesn't have the provided @member, or it is malformed, the
- * @flag_if_unknown will be returned.
- * If the parsed JSON array_member has some elements that we can't parse,
- * @flag_if_unknown will be added to the returned flags.
- *
- * Returns: the found flags from the provided @json_obj
- */
-guint
-srt_get_flags_from_json_array (GType flags_type,
-                               JsonObject *json_obj,
-                               const gchar *array_member,
-                               guint flag_if_unknown)
-{
-  JsonArray *array;
-  guint ret = flag_if_unknown;
-
-  g_return_val_if_fail (G_TYPE_IS_FLAGS (flags_type), 0);
-  g_return_val_if_fail (json_obj != NULL, 0);
-  g_return_val_if_fail (array_member != NULL, 0);
-
-  if (json_object_has_member (json_obj, array_member))
-    {
-      array = json_object_get_array_member (json_obj, array_member);
-
-      if (array == NULL)
-        goto out;
-
-      /* We reset the value out because we found the member we were looking for */
-      ret = 0;
-
-      for (guint j = 0; j < json_array_get_length (array); j++)
-        {
-          const gchar *issue_string = json_array_get_string_element (array, j);
-          if (!srt_add_flag_from_nick (flags_type, issue_string, &ret, NULL))
-            ret |= flag_if_unknown;
-        }
-    }
-
-out:
-  return ret;
-}
-
 static void _srt_constructor (void) __attribute__((__constructor__));
 static void
 _srt_constructor (void)
@@ -787,52 +736,6 @@ _srt_rm_rf (const char *directory)
   return TRUE;
 }
 
-/**
- * _srt_json_array_to_strv:
- * @json_obj: (not nullable): A JSON Object used to search for
- *  @array_member property
- * @array_member: (not nullable): The JSON member to look up
- *
- * Every %NULL or non string members of the array are replaced with the value
- * "<invalid>".
- *
- * Returns: (transfer full) (array zero-terminated=1) (element-type utf8) (nullable):
- *  A string array from the given @json_obj, or %NULL if it doesn't have a
- *  property @array_member
- */
-gchar **
-_srt_json_array_to_strv (JsonObject *json_obj,
-                         const gchar *array_member)
-{
-  JsonArray *array;
-  guint length;
-  const gchar *element;
-  gchar **ret = NULL;
-
-  g_return_val_if_fail (json_obj != NULL, NULL);
-  g_return_val_if_fail (array_member != NULL, NULL);
-
-  if (json_object_has_member (json_obj, array_member))
-    {
-      array = json_object_get_array_member (json_obj, array_member);
-      if (array == NULL)
-        return ret;
-
-      length = json_array_get_length (array);
-      ret = g_new0 (gchar *, length + 1);
-      for (guint i = 0; i < length; i++)
-        {
-          element = json_array_get_string_element (array, i);
-          if (element == NULL)
-            element = "<invalid>";
-          ret[i] = g_strdup (element);
-        }
-      ret[length] = NULL;
-    }
-
-  return ret;
-}
-
 /*
  * _srt_divert_stdout_to_stderr:
  * @error: Used to raise an error on failure
diff --git a/steam-runtime-tools/xdg-portal.c b/steam-runtime-tools/xdg-portal.c
index 5c32df417370036bcc476f95588bd02015913d4f..fb3c56a950fdec881d84c1bbb149da4c0057d02c 100644
--- a/steam-runtime-tools/xdg-portal.c
+++ b/steam-runtime-tools/xdg-portal.c
@@ -28,6 +28,7 @@
 #include "steam-runtime-tools/enums.h"
 #include "steam-runtime-tools/glib-backports-internal.h"
 #include "steam-runtime-tools/json-glib-backports-internal.h"
+#include "steam-runtime-tools/json-utils-internal.h"
 #include "steam-runtime-tools/xdg-portal-internal.h"
 #include "steam-runtime-tools/utils.h"
 #include "steam-runtime-tools/utils-internal.h"
diff --git a/tests/json-utils.c b/tests/json-utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..6044a425e177138023f8c70dc15e064f3e7450dc
--- /dev/null
+++ b/tests/json-utils.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright © 2019-2020 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <steam-runtime-tools/steam-runtime-tools.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+
+#include "steam-runtime-tools/glib-backports-internal.h"
+#include "steam-runtime-tools/json-glib-backports-internal.h"
+#include "steam-runtime-tools/json-utils-internal.h"
+
+typedef struct
+{
+  int unused;
+} Fixture;
+
+typedef struct
+{
+  int unused;
+} Config;
+
+static void
+setup (Fixture *f,
+       gconstpointer context)
+{
+  G_GNUC_UNUSED const Config *config = context;
+}
+
+static void
+teardown (Fixture *f,
+          gconstpointer context)
+{
+  G_GNUC_UNUSED const Config *config = context;
+}
+
+static void
+test_dup_array_of_lines_member (Fixture *f,
+                                gconstpointer context)
+{
+  g_autoptr(JsonArray) arr = json_array_new ();
+  g_autoptr(JsonNode) arr_node = json_node_alloc ();
+  g_autoptr(JsonObject) obj = json_object_new ();
+  g_autofree gchar *missing = NULL;
+  g_autofree gchar *not_array = NULL;
+  g_autofree gchar *text = NULL;
+
+  json_array_add_string_element (arr, "one");
+  json_array_add_string_element (arr, "two\n");
+  json_array_add_object_element (arr, json_object_new ());
+  json_array_add_string_element (arr, "four");
+  json_node_init_array (arr_node, arr);
+  json_object_set_member (obj, "arr", g_steal_pointer (&arr_node));
+  json_object_set_double_member (obj, "not-array", 42.0);
+
+  missing = _srt_json_object_dup_array_of_lines_member (obj, "missing");
+  g_assert_null (missing);
+
+  not_array = _srt_json_object_dup_array_of_lines_member (obj, "not-array");
+  g_assert_null (not_array);
+
+  text = _srt_json_object_dup_array_of_lines_member (obj, "arr");
+  g_assert_cmpstr (text, ==,
+                   "one\n"
+                   "two\n"
+                   "\n"
+                   "four\n");
+}
+
+static void
+test_dup_strv_member (Fixture *f,
+                      gconstpointer context)
+{
+  g_autoptr(JsonArray) arr = json_array_new ();
+  g_autoptr(JsonNode) arr_node = json_node_alloc ();
+  g_autoptr(JsonObject) obj = json_object_new ();
+  g_autoptr(JsonObject) empty = json_object_new ();
+  g_auto(GStrv) missing = NULL;
+  g_auto(GStrv) not_array = NULL;
+  g_auto(GStrv) with_placeholder = NULL;
+  g_auto(GStrv) without_placeholder = NULL;
+
+  json_array_add_string_element (arr, "one");
+  json_array_add_string_element (arr, "two");
+  json_array_add_object_element (arr, g_steal_pointer (&empty));
+  json_array_add_string_element (arr, "four");
+  json_node_init_array (arr_node, arr);
+  json_object_set_member (obj, "arr", g_steal_pointer (&arr_node));
+  json_object_set_double_member (obj, "not-array", 42.0);
+
+  missing = _srt_json_object_dup_strv_member (obj, "missing", NULL);
+  g_assert_null (missing);
+
+  not_array = _srt_json_object_dup_strv_member (obj, "not-array", NULL);
+  g_assert_null (not_array);
+
+  with_placeholder = _srt_json_object_dup_strv_member (obj, "arr", "?!");
+  g_assert_nonnull (with_placeholder);
+  g_assert_cmpstr (with_placeholder[0], ==, "one");
+  g_assert_cmpstr (with_placeholder[1], ==, "two");
+  g_assert_cmpstr (with_placeholder[2], ==, "?!");
+  g_assert_cmpstr (with_placeholder[3], ==, "four");
+  g_assert_cmpstr (with_placeholder[4], ==, NULL);
+
+  without_placeholder = _srt_json_object_dup_strv_member (obj, "arr", NULL);
+  g_assert_nonnull (without_placeholder);
+  g_assert_cmpstr (without_placeholder[0], ==, "one");
+  g_assert_cmpstr (without_placeholder[1], ==, "two");
+  g_assert_cmpstr (without_placeholder[2], ==, "four");
+  g_assert_cmpstr (without_placeholder[3], ==, NULL);
+}
+
+int
+main (int argc,
+      char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add ("/json-utils/dup-array-of-lines-member", Fixture, NULL,
+              setup, test_dup_array_of_lines_member, teardown);
+  g_test_add ("/json-utils/dup-strv-member", Fixture, NULL,
+              setup, test_dup_strv_member, teardown);
+
+  return g_test_run ();
+}
diff --git a/tests/meson.build b/tests/meson.build
index 84e24153020712d2beebf37eaea2dea76a3e0072..b2d5b1db772a355ebc60b5adc754d730136059b9 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -31,6 +31,7 @@ tests = [
   {'name': 'architecture'},
   {'name': 'desktop-entry'},
   {'name': 'graphics'},
+  {'name': 'json-utils', 'static': true},
   {'name': 'library'},
   {'name': 'locale'},
   {'name': 'system-info'},