From ba1ed2da4be47728f8b05891a6eafa182fb0f5c0 Mon Sep 17 00:00:00 2001
From: schellingb <14200249+schellingb@users.noreply.github.com>
Date: Mon, 12 Oct 2020 03:02:20 +0900
Subject: [PATCH 1/3] New faster json parser/writer library rjson Replace
 existing libraries jsonsax_full and jsonsax with it

---
 Makefile.common                               |    3 +-
 cheevos/cheevos_parser.c                      |  514 +++---
 disk_index_file.c                             |  202 +--
 griffin/griffin.c                             |    7 +-
 libretro-common/formats/json/rjson.c          | 1378 +++++++++++++++++
 libretro-common/include/formats/rjson.h       |  263 ++++
 network/netplay/netplay_room_parse.c          |  286 ++--
 .../project.pbxproj                           |    6 +-
 playlist.c                                    | 1180 +++++---------
 runtime_file.c                                |  196 +--
 samples/tasks/database/Makefile               |    3 +-
 11 files changed, 2487 insertions(+), 1551 deletions(-)
 create mode 100644 libretro-common/formats/json/rjson.c
 create mode 100644 libretro-common/include/formats/rjson.h

diff --git a/Makefile.common b/Makefile.common
index 79c86feb2c..22fa99f811 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1963,8 +1963,7 @@ ifeq ($(HAVE_RBMP), 1)
 endif
 
 OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp_encode.o \
-       $(LIBRETRO_COMM_DIR)/formats/json/jsonsax.o \
-       $(LIBRETRO_COMM_DIR)/formats/json/jsonsax_full.o \
+       $(LIBRETRO_COMM_DIR)/formats/json/rjson.o \
        $(LIBRETRO_COMM_DIR)/formats/image_transfer.o \
        $(LIBRETRO_COMM_DIR)/formats/m3u/m3u_file.o
 
diff --git a/cheevos/cheevos_parser.c b/cheevos/cheevos_parser.c
index 6fcfc50537..fe70e20723 100644
--- a/cheevos/cheevos_parser.c
+++ b/cheevos/cheevos_parser.c
@@ -3,7 +3,7 @@
 #include "util.h"
 
 #include <encodings/utf.h>
-#include <formats/jsonsax.h>
+#include <formats/rjson.h>
 #include <string/stdstring.h>
 #include <compat/strl.h>
 
@@ -33,12 +33,20 @@
 
 typedef struct
 {
-   const char *value;
+   char*       value;
    int         is_key;
    size_t      length;
    unsigned    key_hash;
 } rcheevos_getvalueud_t;
 
+#define CHEEVOS_RJSON_OPTIONS                               \
+      /* Inside the field RichPresencePatch newlines are
+       * encoded as '\r\n'. This will filter the \r out. */ \
+        RJSON_OPTION_IGNORE_STRING_CARRIAGE_RETURN          \
+      /* This matches the behavior of the previously used
+       * json parser. It is probably not required */        \
+      | RJSON_OPTION_ALLOW_TRAILING_DATA
+
 /*****************************************************************************
 Gets a value in a JSON
 *****************************************************************************/
@@ -54,102 +62,87 @@ static uint32_t rcheevos_djb2(const char* str, size_t length)
    return hash;
 }
 
-static int rcheevos_getvalue_key(void* userdata,
+static bool rcheevos_getvalue_key(void* userdata,
       const char* name, size_t length)
 {
    rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
 
    ud->is_key = rcheevos_djb2(name, length) == ud->key_hash;
-   return 0;
+   return true;
 }
 
-static int rcheevos_getvalue_string(void* userdata,
+static bool rcheevos_getvalue_string(void* userdata,
       const char* string, size_t length)
 {
    rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
 
-   if (ud->is_key)
+   if (ud->is_key && ud->length > length)
    {
-      ud->value  = string;
-      ud->length = length;
-      ud->is_key = 0;
+      strlcpy(ud->value, string, ud->length);
+      ud->is_key = 2;
+      return false;
    }
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_getvalue_boolean(void* userdata, int istrue)
+static bool rcheevos_getvalue_boolean(void* userdata, bool istrue)
 {
    rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
 
    if (ud->is_key)
    {
-      if (istrue)
+      if (istrue && ud->length > 4)
       {
-         ud->value  = "true";
-         ud->length = 4;
+         strlcpy(ud->value, "true", ud->length);
+         ud->is_key = 2;
+         return false;
       }
-      else
+      if (!istrue && ud->length > 5)
       {
-         ud->value  = "false";
-         ud->length = 5;
+         strlcpy(ud->value, "false", ud->length);
+         ud->is_key = 2;
+         return false;
       }
-
-      ud->is_key    = 0;
    }
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_getvalue_null(void* userdata)
+static bool rcheevos_getvalue_null(void* userdata)
 {
    rcheevos_getvalueud_t* ud = (rcheevos_getvalueud_t*)userdata;
 
-   if (ud->is_key )
+   if (ud->is_key && ud->length > 4)
    {
-      ud->value = "null";
-      ud->length = 4;
-      ud->is_key = 0;
+      strlcpy(ud->value, "null", ud->length);
+      ud->is_key = 2;
+      return false;
    }
 
-   return 0;
+   return true;
 }
 
 static int rcheevos_get_value(const char* json, unsigned key_hash,
       char* value, size_t length)
 {
-   static const jsonsax_handlers_t handlers =
-   {
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      rcheevos_getvalue_key,
-      NULL,
-      rcheevos_getvalue_string,
-      rcheevos_getvalue_string, /* number */
-      rcheevos_getvalue_boolean,
-      rcheevos_getvalue_null
-   };
-
    rcheevos_getvalueud_t ud;
 
    ud.key_hash = key_hash;
    ud.is_key   = 0;
-   ud.value    = NULL;
-   ud.length   = 0;
+   ud.value    = value;
+   ud.length   = length;
    *value      = 0;
 
-   if ((jsonsax_parse(json, &handlers, (void*)&ud) == JSONSAX_OK)
-         && ud.value && ud.length < length)
-   {
-      strlcpy(value, ud.value, ud.length + 1);
-      return 0;
-   }
+   rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
+         rcheevos_getvalue_key,
+         rcheevos_getvalue_string,
+         rcheevos_getvalue_string, /* number */
+         NULL, NULL, NULL, NULL,
+         rcheevos_getvalue_boolean,
+         rcheevos_getvalue_null, NULL);
 
-   return -1;
+   return (ud.is_key == 2 ? 0 : -1);
 }
 
 /*****************************************************************************
@@ -186,16 +179,16 @@ typedef struct
    unsigned lboard_count;
 } rcheevos_countud_t;
 
-static int rcheevos_count_end_array(void* userdata)
+static bool rcheevos_count_end_array(void* userdata)
 {
   rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata;
 
    ud->in_cheevos       = 0;
    ud->in_lboards       = 0;
-   return 0;
+   return true;
 }
 
-static int rcheevos_count_key(void* userdata,
+static bool rcheevos_count_key(void* userdata,
       const char* name, size_t length)
 {
    rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata;
@@ -209,10 +202,10 @@ static int rcheevos_count_key(void* userdata,
    else if (ud->field_hash == CHEEVOS_JSON_KEY_ERROR)
       ud->has_error  = 1;
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_count_number(void* userdata,
+static bool rcheevos_count_number(void* userdata,
       const char* number, size_t length)
 {
    rcheevos_countud_t* ud = (rcheevos_countud_t*)userdata;
@@ -229,30 +222,14 @@ static int rcheevos_count_number(void* userdata,
    else if (ud->in_lboards && ud->field_hash == CHEEVOS_JSON_KEY_ID)
       ud->lboard_count++;
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_count_cheevos(const char* json,
+static bool rcheevos_count_cheevos(const char* json,
       unsigned* core_count, unsigned* unofficial_count,
       unsigned* lboard_count, int* has_error)
 {
-   static const jsonsax_handlers_t handlers =
-   {
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      rcheevos_count_end_array,
-      rcheevos_count_key,
-      NULL,
-      NULL,
-      rcheevos_count_number,
-      NULL,
-      NULL
-   };
-
-   int res;
+   bool res;
    rcheevos_countud_t ud;
    ud.in_cheevos       = 0;
    ud.in_lboards       = 0;
@@ -261,7 +238,14 @@ static int rcheevos_count_cheevos(const char* json,
    ud.unofficial_count = 0;
    ud.lboard_count     = 0;
 
-   res                 = jsonsax_parse(json, &handlers, (void*)&ud);
+
+   res = rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
+               rcheevos_count_key,
+               NULL,
+               rcheevos_count_number,
+               NULL, NULL, NULL,
+               rcheevos_count_end_array,
+               NULL, NULL, NULL);
 
    *core_count         = ud.core_count;
    *unofficial_count   = ud.unofficial_count;
@@ -275,168 +259,92 @@ static int rcheevos_count_cheevos(const char* json,
 Parses the cheevos in the JSON
 *****************************************************************************/
 
-typedef struct
-{
-   const char* string;
-   size_t      length;
-} rcheevos_field_t;
-
 typedef struct
 {
    int      in_cheevos;
    int      in_lboards;
-   int      is_game_id;
-   int      is_title;
-   int      is_console_id;
-   int      is_richpresence;
+   int      lboard_had_id;
    unsigned core_count;
    unsigned unofficial_count;
    unsigned lboard_count;
 
-   rcheevos_field_t* field;
-   rcheevos_field_t  id, memaddr, title, desc, points, author;
-   rcheevos_field_t  modified, created, badge, flags, format;
+   unsigned     cheevo_flags;
+   const char*  lboard_format;
+   const char** field_string;
+   unsigned*    field_unsigned;
+
+   rcheevos_racheevo_t cheevo;
+   rcheevos_ralboard_t lboard;
 
    rcheevos_rapatchdata_t* patchdata;
 } rcheevos_readud_t;
 
-static char* rcheevos_unescape_string(const char* string, size_t length)
-{
-   const char* end = string + length;
-   char* buffer    = (char*)malloc(length + 1);
-   char* buffer_it = buffer;
-
-   if (!buffer)
-      return NULL;
-
-   while (string < end)
-   {
-      if (*string == '\\')
-      {
-         char escaped_char = string[1];
-         switch (escaped_char)
-         {
-            case 'r': /* Ignore carriage return */
-               string += 2;
-               break;
-
-            case 'n': /* Accept newlines */
-               *buffer_it++ = '\n';
-               string += 2;
-               break;
-
-            case 'u': /* Accept UTF-16 unicode characters */
-            {
-#define MAX_SEQUENCES 16
-               uint16_t  utf16[MAX_SEQUENCES];
-               char      utf8[MAX_SEQUENCES * 4];
-               uint8_t   i, j;
-
-               for (i = 1; i < MAX_SEQUENCES - 1; i++)
-                  if (strncmp((string + 6 * i), "\\u", 2))
-                     break;
-
-               /* Get escaped hex values and add them to the string */
-               for (j = 0; j < i; j++)
-               {
-                  char temp[5];
-
-                  string   += 2;
-                  memcpy(temp, string, 4);
-                  temp[4]   = '\0';
-                  utf16[j]  = string_hex_to_unsigned(temp);
-                  string   += 4;
-               }
-               utf16[j] = 0;
-
-               if (utf16_to_char_string(utf16, utf8, sizeof(utf8)))
-               {
-                  size_t utf8_len = strlen(utf8);
-                  memcpy(buffer_it, utf8, utf8_len);
-                  buffer_it += utf8_len;
-               }
-            }
-            break;
-
-            default:
-               *buffer_it++ = escaped_char;
-               string += 2;
-               break;
-         }
-      }
-      else
-         *buffer_it++ = *string++;
-   }
-   *buffer_it = '\0';
-
-   return buffer;
-}
-
-static int rcheevos_new_cheevo(rcheevos_readud_t* ud)
+static bool rcheevos_new_cheevo(rcheevos_readud_t* ud)
 {
    rcheevos_racheevo_t* cheevo = NULL;
-   unsigned flags             = (unsigned)strtol(ud->flags.string, NULL, 10);
 
-   if (flags == 3)
+   if (ud->cheevo_flags == 3)
       cheevo = ud->patchdata->core + ud->core_count++;
-   else if (flags == 5)
+   else if (ud->cheevo_flags == 5)
       cheevo = ud->patchdata->unofficial + ud->unofficial_count++;
-   else
-      return 0;
+   ud->cheevo_flags = 0;
 
-   cheevo->title       = rcheevos_unescape_string(ud->title.string, ud->title.length);
-   cheevo->description = rcheevos_unescape_string(ud->desc.string, ud->desc.length);
-   cheevo->badge       = rcheevos_unescape_string(ud->badge.string, ud->badge.length);
-   cheevo->memaddr     = rcheevos_unescape_string(ud->memaddr.string, ud->memaddr.length);
-   cheevo->points      = (unsigned)strtol(ud->points.string, NULL, 10);
-   cheevo->id          = (unsigned)strtol(ud->id.string, NULL, 10);
-
-   if (   !cheevo->title
-       || !cheevo->description
-       || !cheevo->badge
-       || !cheevo->memaddr)
+   if (!cheevo
+       || !ud->cheevo.title
+       || !ud->cheevo.description
+       || !ud->cheevo.badge
+       || !ud->cheevo.memaddr)
    {
-      CHEEVOS_FREE(cheevo->title);
-      CHEEVOS_FREE(cheevo->description);
-      CHEEVOS_FREE(cheevo->badge);
-      CHEEVOS_FREE(cheevo->memaddr);
-      return -1;
+      CHEEVOS_FREE(ud->cheevo.title);
+      CHEEVOS_FREE(ud->cheevo.description);
+      CHEEVOS_FREE(ud->cheevo.badge);
+      CHEEVOS_FREE(ud->cheevo.memaddr);
+      memset(&ud->cheevo, 0, sizeof(ud->cheevo));
+      return (cheevo ? false : true);
    }
 
-   return 0;
+   *cheevo = ud->cheevo;
+   memset(&ud->cheevo, 0, sizeof(ud->cheevo));
+
+   return true;
 }
 
-static int rcheevos_new_lboard(rcheevos_readud_t* ud)
+static bool rcheevos_new_lboard(rcheevos_readud_t* ud)
 {
-   rcheevos_ralboard_t* lboard = ud->patchdata->lboards + ud->lboard_count++;
-   char format[32];
+   rcheevos_ralboard_t* lboard = NULL;
 
-   lboard->title       = rcheevos_unescape_string(ud->title.string, ud->title.length);
-   lboard->description = rcheevos_unescape_string(ud->desc.string, ud->desc.length);
-   lboard->mem         = rcheevos_unescape_string(ud->memaddr.string, ud->memaddr.length);
-   lboard->id          = (unsigned)strtol(ud->id.string, NULL, 10);
+   if (ud->lboard_had_id)
+      lboard = ud->patchdata->lboards + ud->lboard_count++;
+   ud->lboard_had_id = 0;
 
-   if (   !lboard->title
-       || !lboard->description
-       || !lboard->mem)
+   if (!lboard
+       || !ud->lboard.title
+       || !ud->lboard.description
+       || !ud->lboard.mem)
    {
-      CHEEVOS_FREE(lboard->title);
-      CHEEVOS_FREE(lboard->description);
-      CHEEVOS_FREE(lboard->mem);
-      return -1;
+      CHEEVOS_FREE(ud->lboard.title);
+      CHEEVOS_FREE(ud->lboard.description);
+      CHEEVOS_FREE(ud->lboard.mem);
+      memset(&ud->lboard, 0, sizeof(ud->lboard));
+      CHEEVOS_FREE(ud->lboard_format);
+      ud->lboard_format = NULL;
+      return (lboard ? false : true);
    }
 
-   if (ud->format.length > 0 && ud->format.length < sizeof(format) - 1)
+   *lboard = ud->lboard;
+   memset(&ud->lboard, 0, sizeof(ud->lboard));
+
+   if (ud->lboard_format)
    {
-      memcpy(format, ud->format.string, ud->format.length);
-      format[ud->format.length] = '\0';
-      lboard->format = rc_parse_format(format);
+      lboard->format = rc_parse_format(ud->lboard_format);
+      CHEEVOS_FREE(ud->lboard_format);
+      ud->lboard_format = NULL;
    }
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_read_end_object(void* userdata)
+static bool rcheevos_read_end_object(void* userdata)
 {
    rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
 
@@ -446,26 +354,26 @@ static int rcheevos_read_end_object(void* userdata)
    if (ud->in_lboards)
       return rcheevos_new_lboard(ud);
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_read_end_array(void* userdata)
+static bool rcheevos_read_end_array(void* userdata)
 {
    rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
 
    ud->in_cheevos       = 0;
    ud->in_lboards       = 0;
-   return 0;
+   return true;
 }
 
-static int rcheevos_read_key(void* userdata,
+static bool rcheevos_read_key(void* userdata,
       const char* name, size_t length)
 {
    rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
 
-   int common           = ud->in_cheevos || ud->in_lboards;
    uint32_t hash        = rcheevos_djb2(name, length);
-   ud->field            = NULL;
+   ud->field_unsigned   = NULL;
+   ud->field_string     = NULL;
 
    switch (hash)
    {
@@ -476,145 +384,121 @@ static int rcheevos_read_key(void* userdata,
          ud->in_lboards = 1;
          break;
       case CHEEVOS_JSON_KEY_CONSOLE_ID:
-         ud->is_console_id = 1;
+         ud->field_unsigned = &ud->patchdata->console_id;
          break;
       case CHEEVOS_JSON_KEY_RICHPRESENCE:
-         ud->is_richpresence = 1;
+         ud->field_string = (const char**)&ud->patchdata->richpresence_script;
          break;
       case CHEEVOS_JSON_KEY_ID:
-         if (common)
-            ud->field = &ud->id;
+         if (ud->in_cheevos)
+            ud->field_unsigned = &ud->cheevo.id;
+         else if (ud->in_lboards)
+         {
+            ud->field_unsigned = &ud->lboard.id;
+            ud->lboard_had_id = 1;
+         }
          else
-            ud->is_game_id = 1;
+            ud->field_unsigned = &ud->patchdata->game_id;
          break;
       case CHEEVOS_JSON_KEY_MEMADDR:
          if (ud->in_cheevos)
-            ud->field = &ud->memaddr;
+            ud->field_string = &ud->cheevo.memaddr;
          break;
       case CHEEVOS_JSON_KEY_MEM:
          if (ud->in_lboards)
-            ud->field = &ud->memaddr;
+            ud->field_string = &ud->lboard.mem;
          break;
       case CHEEVOS_JSON_KEY_TITLE:
-         if (common)
-            ud->field = &ud->title;
+         if (ud->in_cheevos)
+            ud->field_string = &ud->cheevo.title;
+         else if (ud->in_lboards)
+            ud->field_string = &ud->lboard.title;
          else
-            ud->is_title = 1;
+            ud->field_string = (const char**)&ud->patchdata->title;
          break;
       case CHEEVOS_JSON_KEY_DESCRIPTION:
-         if (common)
-            ud->field = &ud->desc;
+         if (ud->in_cheevos)
+            ud->field_string = &ud->cheevo.description;
+         else if (ud->in_lboards)
+            ud->field_string = &ud->lboard.description;
          break;
       case CHEEVOS_JSON_KEY_POINTS:
          if (ud->in_cheevos)
-            ud->field = &ud->points;
+            ud->field_unsigned = &ud->cheevo.points;
          break;
+      /* UNUSED
       case CHEEVOS_JSON_KEY_AUTHOR:
          if (ud->in_cheevos)
-            ud->field = &ud->author;
+            ud->field_string = &ud->cheevo.author;
          break;
       case CHEEVOS_JSON_KEY_MODIFIED:
          if (ud->in_cheevos)
-            ud->field = &ud->modified;
+            ud->field_string = &ud->cheevo.modified;
          break;
       case CHEEVOS_JSON_KEY_CREATED:
          if (ud->in_cheevos)
-            ud->field = &ud->created;
-         break;
+            ud->field_string = &ud->cheevo.created;
+         break; */
       case CHEEVOS_JSON_KEY_BADGENAME:
          if (ud->in_cheevos)
-            ud->field = &ud->badge;
+            ud->field_string = &ud->cheevo.badge;
          break;
       case CHEEVOS_JSON_KEY_FLAGS:
          if (ud->in_cheevos)
-            ud->field = &ud->flags;
+            ud->field_unsigned = &ud->cheevo_flags;
          break;
       case CHEEVOS_JSON_KEY_FORMAT:
          if (ud->in_lboards)
-            ud->field = &ud->format;
+            ud->field_string = &ud->lboard_format;
          break;
       default:
          break;
    }
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_read_string(void* userdata,
+static bool rcheevos_read_string(void* userdata,
       const char* string, size_t length)
 {
    rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
 
-   if (ud->field)
+   if (ud->field_string)
    {
-      ud->field->string = string;
-      ud->field->length = length;
-   }
-   else if (ud->is_title)
-   {
-      ud->patchdata->title = rcheevos_unescape_string(string, length);
-      ud->is_title         = 0;
-   }
-   else if (ud->is_richpresence)
-   {
-      ud->patchdata->richpresence_script = rcheevos_unescape_string(string, length);
-      ud->is_richpresence                = 0;
+      if (*ud->field_string)
+         CHEEVOS_FREE((*ud->field_string));
+      *ud->field_string = strdup(string);
+      ud->field_string = NULL;
    }
 
-   return 0;
+   return true;
 }
 
-static int rcheevos_read_number(void* userdata,
+static bool rcheevos_read_number(void* userdata,
       const char* number, size_t length)
 {
    rcheevos_readud_t* ud = (rcheevos_readud_t*)userdata;
 
-   if (ud->field)
+   if (ud->field_unsigned)
    {
-      ud->field->string = number;
-      ud->field->length = length;
-   }
-   else if (ud->is_game_id)
-   {
-      ud->patchdata->game_id = (unsigned)strtol(number, NULL, 10);
-      ud->is_game_id         = 0;
-   }
-   else if (ud->is_console_id)
-   {
-      ud->patchdata->console_id = (unsigned)strtol(number, NULL, 10);
-      ud->is_console_id         = 0;
+      *ud->field_unsigned = (unsigned)strtol(number, NULL, 10);
+      ud->field_unsigned = NULL;
    }
 
-   return 0;
+   return true;
 }
 
 int rcheevos_get_patchdata(const char* json, rcheevos_rapatchdata_t* patchdata)
 {
-   static const jsonsax_handlers_t handlers =
-   {
-      NULL,
-      NULL,
-      NULL,
-      rcheevos_read_end_object,
-      NULL,
-      rcheevos_read_end_array,
-      rcheevos_read_key,
-      NULL,
-      rcheevos_read_string,
-      rcheevos_read_number,
-      NULL,
-      NULL
-   };
-
    rcheevos_readud_t ud;
-   int res;
+   bool res;
    int has_error;
 
    /* Count the number of achievements in the JSON file. */
    res = rcheevos_count_cheevos(json, &patchdata->core_count,
       &patchdata->unofficial_count, &patchdata->lboard_count, &has_error);
 
-   if (res != JSONSAX_OK || has_error)
+   if (!res || has_error)
       return -1;
 
    /* Allocate the achievements. */
@@ -643,19 +527,27 @@ int rcheevos_get_patchdata(const char* json, rcheevos_rapatchdata_t* patchdata)
    patchdata->title = NULL;
 
    /* Load the achievements. */
-   ud.in_cheevos       = 0;
-   ud.in_lboards       = 0;
-   ud.is_game_id       = 0;
-   ud.is_title         = 0;
-   ud.is_console_id    = 0;
-   ud.is_richpresence  = 0;
-   ud.field            = NULL;
-   ud.core_count       = 0;
-   ud.unofficial_count = 0;
-   ud.lboard_count     = 0;
+   memset(&ud, 0, sizeof(ud));
    ud.patchdata        = patchdata;
 
-   if (jsonsax_parse(json, &handlers, (void*)&ud) != JSONSAX_OK)
+   res = rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
+            rcheevos_read_key,
+            rcheevos_read_string,
+            rcheevos_read_number,
+            NULL, rcheevos_read_end_object,
+            NULL, rcheevos_read_end_array,
+            NULL, NULL, NULL);
+
+   CHEEVOS_FREE(ud.cheevo.title);
+   CHEEVOS_FREE(ud.cheevo.description);
+   CHEEVOS_FREE(ud.cheevo.badge);
+   CHEEVOS_FREE(ud.cheevo.memaddr);
+   CHEEVOS_FREE(ud.lboard.title);
+   CHEEVOS_FREE(ud.lboard.description);
+   CHEEVOS_FREE(ud.lboard.mem);
+   CHEEVOS_FREE(ud.lboard_format);
+
+   if (!res)
    {
       rcheevos_free_patchdata(patchdata);
       return -1;
@@ -730,15 +622,23 @@ typedef struct
    void* userdata;
 } rcheevos_deactivate_t;
 
-static int rcheevos_deactivate_index(void* userdata, unsigned int index)
+static bool rcheevos_deactivate_elements_begin(void* userdata)
 {
    rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata;
 
    ud->is_element           = 1;
-   return 0;
+   return true;
 }
 
-static int rcheevos_deactivate_number(void* userdata,
+static bool rcheevos_deactivate_elements_stop(void* userdata)
+{
+   rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata;
+
+   ud->is_element           = 0;
+   return true;
+}
+
+static bool rcheevos_deactivate_number(void* userdata,
       const char* number, size_t length)
 {
    rcheevos_deactivate_t* ud = (rcheevos_deactivate_t*)userdata;
@@ -746,40 +646,30 @@ static int rcheevos_deactivate_number(void* userdata,
 
    if (ud->is_element)
    {
-      ud->is_element = 0;
       id             = (unsigned)strtol(number, NULL, 10);
 
       ud->unlock_cb(id, ud->userdata);
    }
 
-   return 0;
+   return true;
 }
 
 void rcheevos_deactivate_unlocks(const char* json, rcheevos_unlock_cb_t unlock_cb, void* userdata)
 {
-   static const jsonsax_handlers_t handlers =
-   {
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      NULL,
-      rcheevos_deactivate_index,
-      NULL,
-      rcheevos_deactivate_number,
-      NULL,
-      NULL
-   };
-
    rcheevos_deactivate_t ud;
 
    ud.is_element = 0;
    ud.unlock_cb  = unlock_cb;
    ud.userdata   = userdata;
 
-   jsonsax_parse(json, &handlers, (void*)&ud);
+   rjson_parse_quick(json, &ud, CHEEVOS_RJSON_OPTIONS,
+         NULL, NULL,
+         rcheevos_deactivate_number,
+         rcheevos_deactivate_elements_stop,
+         rcheevos_deactivate_elements_stop,
+         rcheevos_deactivate_elements_begin,
+         rcheevos_deactivate_elements_stop,
+         NULL, NULL, NULL);
 }
 
 /*****************************************************************************
diff --git a/disk_index_file.c b/disk_index_file.c
index 99ac739e19..236bbf5ae6 100644
--- a/disk_index_file.c
+++ b/disk_index_file.c
@@ -23,7 +23,7 @@
 #include <file/file_path.h>
 #include <string/stdstring.h>
 #include <streams/file_stream.h>
-#include <formats/jsonsax_full.h>
+#include <formats/rjson.h>
 
 #include "file_path_special.h"
 #include "verbosity.h"
@@ -37,25 +37,20 @@
 
 typedef struct
 {
-   JSON_Parser parser;
-   JSON_Writer writer;
-   RFILE *file;
    unsigned *current_entry_uint_val;
    char **current_entry_str_val;
    unsigned image_index;
    char *image_path;
 } DCifJSONContext;
 
-static JSON_Parser_HandlerResult DCifJSONObjectMemberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes)
+static bool DCifJSONObjectMemberHandler(void* context, const char *pValue, size_t length)
 {
-   DCifJSONContext *pCtx = (DCifJSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   DCifJSONContext *pCtx = (DCifJSONContext*)context;
 
    if (pCtx->current_entry_str_val)
    {
       /* something went wrong */
-      RARCH_ERR("[disk index file] JSON parsing failed at line %d.\n", __LINE__);
-      return JSON_Parser_Abort;
+      return false;
    }
 
    if (length)
@@ -67,13 +62,12 @@ static JSON_Parser_HandlerResult DCifJSONObjectMemberHandler(JSON_Parser parser,
       /* ignore unknown members */
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult DCifJSONNumberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes)
+static bool DCifJSONNumberHandler(void* context, const char *pValue, size_t length)
 {
-   DCifJSONContext *pCtx = (DCifJSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   DCifJSONContext *pCtx = (DCifJSONContext*)context;
 
    if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue))
       *pCtx->current_entry_uint_val = string_to_unsigned(pValue);
@@ -81,13 +75,12 @@ static JSON_Parser_HandlerResult DCifJSONNumberHandler(JSON_Parser parser, char
 
    pCtx->current_entry_uint_val = NULL;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult DCifJSONStringHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes)
+static bool DCifJSONStringHandler(void* context, const char *pValue, size_t length)
 {
-   DCifJSONContext *pCtx = (DCifJSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   DCifJSONContext *pCtx = (DCifJSONContext*)context;
 
    if (pCtx->current_entry_str_val && length && !string_is_empty(pValue))
    {
@@ -100,35 +93,7 @@ static JSON_Parser_HandlerResult DCifJSONStringHandler(JSON_Parser parser, char
 
    pCtx->current_entry_str_val = NULL;
 
-   return JSON_Parser_Continue;
-}
-
-static JSON_Writer_HandlerResult DCifJSONOutputHandler(JSON_Writer writer, const char *pBytes, size_t length)
-{
-   DCifJSONContext *context = (DCifJSONContext*)JSON_Writer_GetUserData(writer);
-   (void)writer; /* unused */
-
-   return filestream_write(context->file, pBytes, length) == length ? JSON_Writer_Continue : JSON_Writer_Abort;
-}
-
-static void DCifJSONLogError(DCifJSONContext *pCtx)
-{
-   if (pCtx->parser && JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler)
-   {
-      JSON_Error error            = JSON_Parser_GetError(pCtx->parser);
-      JSON_Location errorLocation = { 0, 0, 0 };
-
-      (void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation);
-      RARCH_ERR("[disk index file] Error: Invalid JSON at line %d, column %d (input byte %d) - %s.\n",
-            (int)errorLocation.line + 1,
-            (int)errorLocation.column + 1,
-            (int)errorLocation.byte,
-            JSON_ErrorString(error));
-   }
-   else if (pCtx->writer && JSON_Writer_GetError(pCtx->writer) != JSON_Error_AbortedByHandler)
-   {
-      RARCH_ERR("[disk index file] Error: could not write output - %s.\n", JSON_ErrorString(JSON_Writer_GetError(pCtx->writer)));
-   }
+   return true;
 }
 
 /******************/
@@ -156,6 +121,7 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file)
    bool success            = false;
    DCifJSONContext context = {0};
    RFILE *file             = NULL;
+   rjson_t* parser;
 
    /* Sanity check */
    if (!disk_index_file)
@@ -183,66 +149,44 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file)
    }
 
    /* Initialise JSON parser */
-   context.image_index = 0;
-   context.image_path  = NULL;
-   context.parser      = JSON_Parser_Create(NULL);
-   context.file        = file;
-
-   if (!context.parser)
+   parser = rjson_open_rfile(file);
+   if (!parser)
    {
       RARCH_ERR("[disk index file] Failed to create JSON parser.\n");
       goto end;
    }
 
    /* Configure parser */
-   JSON_Parser_SetAllowBOM(context.parser, JSON_True);
-   JSON_Parser_SetNumberHandler(context.parser,       &DCifJSONNumberHandler);
-   JSON_Parser_SetStringHandler(context.parser,       &DCifJSONStringHandler);
-   JSON_Parser_SetObjectMemberHandler(context.parser, &DCifJSONObjectMemberHandler);
-   JSON_Parser_SetUserData(context.parser, &context);
+   rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM);
 
    /* Read file */
-   while (!filestream_eof(file))
+   if (rjson_parse(parser, &context,
+         DCifJSONObjectMemberHandler,
+         DCifJSONStringHandler,
+         DCifJSONNumberHandler,
+         NULL, NULL, NULL, NULL, /* unused object/array handlers */
+         NULL, NULL) /* unused boolean/null handlers */
+         != RJSON_DONE)
    {
-      /* Disk index files are tiny - use small chunk size */
-      char chunk[128] = {0};
-      int64_t length  = filestream_read(file, chunk, sizeof(chunk));
-
-      /* Error checking... */
-      if (!length && !filestream_eof(file))
+      if (rjson_get_source_context_len(parser))
       {
          RARCH_ERR(
-               "[disk index file] Failed to read disk index file: %s\n",
-               file_path);
-         JSON_Parser_Free(context.parser);
-         goto end;
+               "[disk index file] Error parsing chunk of disk index file: %s\n---snip---\n%.*s\n---snip---\n",
+               file_path,
+               rjson_get_source_context_len(parser),
+               rjson_get_source_context_buf(parser));
       }
-
-      /* Parse chunk */
-      if (!JSON_Parser_Parse(context.parser, chunk, (size_t)length, JSON_False))
-      {
-         RARCH_ERR(
-               "[disk index file] Error parsing chunk of disk index file: %s\n---snip---\n%s\n---snip---\n",
-               file_path, chunk);
-         DCifJSONLogError(&context);
-         JSON_Parser_Free(context.parser);
-         goto end;
-      }
-   }
-
-   /* Finalise parsing */
-   if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True))
-   {
       RARCH_WARN(
             "[disk index file] Error parsing disk index file: %s\n",
             file_path);
-      DCifJSONLogError(&context);
-      JSON_Parser_Free(context.parser);
-      goto end;
+      RARCH_ERR("[disk index file] Error: Invalid JSON at line %d, column %d - %s.\n",
+            (int)rjson_get_source_line(parser),
+            (int)rjson_get_source_column(parser),
+            (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
    }
 
    /* Free parser */
-   JSON_Parser_Free(context.parser);
+   rjson_free(parser);
 
    /* Copy values read from JSON file */
    disk_index_file->image_index = context.image_index;
@@ -406,15 +350,11 @@ void disk_index_file_set(
 /* Saves specified disk index file to disk */
 bool disk_index_file_save(disk_index_file_t *disk_index_file)
 {
-   int n;
-   char value_string[32];
    const char *file_path;
-   DCifJSONContext context = {0};
+   rjsonwriter_t* writer;
    RFILE *file             = NULL;
    bool success            = false;
 
-   value_string[0] = '\0';
-
    /* Sanity check */
    if (!disk_index_file)
       return false;
@@ -450,69 +390,53 @@ bool disk_index_file_save(disk_index_file_t *disk_index_file)
    }
 
    /* Initialise JSON writer */
-   context.writer = JSON_Writer_Create(NULL);
-   context.file   = file;
+   writer = rjsonwriter_open_rfile(file);
 
-   if (!context.writer)
+   if (!writer)
    {
       RARCH_ERR("[disk index file] Failed to create JSON writer.\n");
       goto end;
    }
 
-   /* Configure JSON writer */
-   JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8);
-   JSON_Writer_SetOutputHandler(context.writer, &DCifJSONOutputHandler);
-   JSON_Writer_SetUserData(context.writer, &context);
-
    /* Write output file */
-   JSON_Writer_WriteStartObject(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_start_object(writer);
+   rjsonwriter_add_newline(writer);
 
    /* > Version entry */
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "version",
-         STRLEN_CONST("version"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteString(context.writer, "1.0",
-         STRLEN_CONST("1.0"), JSON_UTF8);
-   JSON_Writer_WriteComma(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "version");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_string(writer, "1.0");
+   rjsonwriter_add_comma(writer);
+   rjsonwriter_add_newline(writer);
 
    /* > image index entry */
-   n = snprintf(
-         value_string, sizeof(value_string),
-         "%u", disk_index_file->image_index);
-   if ((n < 0) || (n >= 32))
-      n = 0; /* Silence GCC warnings... */
-
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "image_index",
-         STRLEN_CONST("image_index"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteNumber(context.writer, value_string,
-         strlen(value_string), JSON_UTF8);
-   JSON_Writer_WriteComma(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "image_index");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_unsigned(writer, disk_index_file->image_index);
+   rjsonwriter_add_comma(writer);
+   rjsonwriter_add_newline(writer);
 
    /* > image path entry */
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "image_path",
-         STRLEN_CONST("image_path"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteString(context.writer,
-         disk_index_file->image_path,
-         strlen(disk_index_file->image_path), JSON_UTF8);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "image_path");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_string(writer, disk_index_file->image_path);
+   rjsonwriter_add_newline(writer);
 
    /* > Finalise */
-   JSON_Writer_WriteEndObject(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_end_object(writer);
+   rjsonwriter_add_newline(writer);
 
    /* Free JSON writer */
-   JSON_Writer_Free(context.writer);
+   if (!rjsonwriter_free(writer))
+   {
+      RARCH_ERR("[disk index file] Error writing disk index file: %s\n", file_path);
+   }
 
    /* Changes have been written - record
     * is no longer considered to be in a
diff --git a/griffin/griffin.c b/griffin/griffin.c
index ea1236e77c..6bec2b5e47 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -57,8 +57,6 @@
 #endif
 #endif
 
-#define JSON_STATIC 1 /* must come before runtime_file, netplay_room_parse and jsonsax_full */
-
 #if _MSC_VER && !defined(__WINRT__)
 #include "../libretro-common/compat/compat_snprintf.c"
 #endif
@@ -193,7 +191,6 @@ ACHIEVEMENTS
 #include "../libretro-common/net/net_http.c"
 #endif
 
-#include "../libretro-common/formats/json/jsonsax.c"
 #include "../libretro-common/formats/cdfs/cdfs.c"
 #include "../network/net_http_special.c"
 
@@ -1279,8 +1276,8 @@ THREAD
 #include "../audio/audio_thread_wrapper.c"
 #endif
 
-/* needed for both playlists and netplay lobbies */
-#include "../libretro-common/formats/json/jsonsax_full.c"
+/* needed for playlists, netplay lobbies and achievements */
+#include "../libretro-common/formats/json/rjson.c"
 
 /*============================================================
 NETPLAY
diff --git a/libretro-common/formats/json/rjson.c b/libretro-common/formats/json/rjson.c
new file mode 100644
index 0000000000..a71233f9e5
--- /dev/null
+++ b/libretro-common/formats/json/rjson.c
@@ -0,0 +1,1378 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rjson.c).
+ * ---------------------------------------------------------------------------------------
+ *
+ * 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.
+ */
+
+/* The parser is based on Public Domain JSON Parser for C by Christopher Wellons - https://github.com/skeeto/pdjson */
+
+#include <stdio.h>  /* snprintf, vsnprintf */
+#include <stdarg.h> /* va_list */
+#include <string.h> /* memcpy, strlen */
+#include <stdint.h> /* int64_t */
+#include <stdlib.h> /* malloc, realloc, atof, atoi */
+
+#include <formats/rjson.h>
+#include <compat/posix_string.h>
+#include <streams/interface_stream.h>
+#include <streams/file_stream.h>
+
+struct _rjson_stack { enum rjson_type type; size_t count; };
+
+struct rjson
+{
+   /* Order of the top few struct elements have an impact on performance */
+   /* Place most frequently accessed things on top */
+   const unsigned char *input_p;
+   struct _rjson_stack *stack_top;
+   const unsigned char *input_end;
+   const unsigned char* source_column_p;
+   size_t source_line;
+
+   char *string, *string_pass_through;
+   size_t string_len, string_cap;
+
+   struct _rjson_stack inline_stack[10];
+   struct _rjson_stack *stack;
+
+   rjson_io_t io;
+   void *user_data;
+
+   unsigned int stack_cap, stack_max;
+   int input_len;
+
+   char option_flags;
+   char decimal_sep;
+   char error_text[80];
+   char inline_string[512];
+
+   /* Must be at the end of the struct, can be allocated with custom size */
+   unsigned char input_buf[512];
+};
+
+enum _rjson_token
+{
+   _rJSON_TOK_WHITESPACE, _rJSON_TOK_NEWLINE, _rJSON_TOK_OPTIONAL_SKIP,
+   _rJSON_TOK_OBJECT, _rJSON_TOK_ARRAY, _rJSON_TOK_STRING, _rJSON_TOK_NUMBER,
+   _rJSON_TOK_TRUE, _rJSON_TOK_FALSE, _rJSON_TOK_NULL,
+   _rJSON_TOK_OBJECT_END, _rJSON_TOK_ARRAY_END, _rJSON_TOK_COLON,
+   _rJSON_TOK_COMMA, _rJSON_TOK_ERROR, _rJSON_TOK_EOF, _rJSON_TOKCOUNT
+};
+
+/* The used char type is int and not short for better performance */
+typedef unsigned int _rjson_char_t;
+#define _rJSON_EOF ((_rjson_char_t)256)
+
+/* Compiler branching hint for expression with high probability
+ * Explicitly only have likely (and no unlikely) because compilers
+ * that don't support it expect likely branches to come first. */
+#if defined(__GNUC__) || defined(__clang__)
+#define _rJSON_LIKELY(x) __builtin_expect(!!(x), 1)
+#else
+#define _rJSON_LIKELY(x) (x)
+#endif
+
+/* These 3 error functions return RJSON_ERROR for convenience */
+static enum rjson_type _rjson_error(rjson_t *json, const char *fmt, ...)
+{
+   va_list ap;
+   if (json->stack_top->type == RJSON_ERROR) return RJSON_ERROR;
+   json->stack_top->type = RJSON_ERROR;
+   va_start(ap, fmt);
+   vsnprintf(json->error_text, sizeof(json->error_text), fmt, ap);
+   va_end(ap);
+   return RJSON_ERROR;
+}
+
+static enum rjson_type _rjson_error_char(rjson_t *json,
+      const char *fmt, _rjson_char_t chr)
+{
+   char buf[16];
+   if (json->stack_top->type == RJSON_ERROR) return RJSON_ERROR;
+   snprintf(buf, sizeof(buf),
+         (chr == _rJSON_EOF ? "end of stream" :
+         (chr >= ' ' && chr <= '~' ? "'%c'" : "byte 0x%02X")), chr);
+   return _rjson_error(json, fmt, buf);
+}
+
+static enum rjson_type _rjson_error_token(rjson_t *json,
+   const char *fmt, enum _rjson_token tok)
+{
+   return _rjson_error_char(json, fmt,
+         (tok == _rJSON_TOK_EOF ? _rJSON_EOF : json->input_p[-1]));
+}
+
+static bool _rjson_io_input(rjson_t *json)
+{
+   if (json->input_end == json->input_buf) return false;
+   json->source_column_p -= (json->input_end - json->input_buf);
+   json->input_p = json->input_buf;
+   json->input_end = json->input_buf +
+         json->io(json->input_buf, json->input_len, json->user_data);
+   if (json->input_end < json->input_buf)
+   {
+      _rjson_error(json, "input stream read error");
+      json->input_end = json->input_buf;
+   }
+   return (json->input_end != json->input_p);
+}
+
+static bool _rjson_grow_string(rjson_t *json)
+{
+   char *string;
+   size_t new_string_cap = json->string_cap * 2;
+   if (json->string != json->inline_string)
+      string = (char*)realloc(json->string, new_string_cap);
+   else if ((string = (char*)malloc(new_string_cap)) != NULL)
+      memcpy(string, json->inline_string, sizeof(json->inline_string));
+   if (string == NULL)
+   {
+      _rjson_error(json, "out of memory");
+      return false;
+   }
+   json->string_cap = new_string_cap;
+   json->string = string;
+   return true;
+}
+
+static INLINE bool _rjson_pushchar(rjson_t *json, _rjson_char_t c)
+{
+   json->string[json->string_len++] = (char)c;
+   return (json->string_len != json->string_cap || _rjson_grow_string(json));
+}
+
+static INLINE bool _rjson_pushchars(rjson_t *json,
+      const unsigned char *from, const unsigned char *to)
+{
+   size_t len = json->string_len, new_len = len + (to - from);
+   unsigned char* string;
+   while (new_len >= json->string_cap)
+      if (!_rjson_grow_string(json))
+         return false;
+   string = (unsigned char *)json->string;
+   while (len != new_len) string[len++] = *(from++);
+   json->string_len = new_len;
+   return true;
+}
+
+static INLINE _rjson_char_t _rjson_char_get(rjson_t *json)
+{
+   return (json->input_p != json->input_end || _rjson_io_input(json)
+        ? *json->input_p++ : _rJSON_EOF);
+}
+
+static unsigned int _rjson_get_unicode_cp(rjson_t *json)
+{
+   unsigned int cp = 0, shift = 16;
+   for (;;)
+   {
+      _rjson_char_t c = _rjson_char_get(json);
+      switch (c)
+      {
+         case '0': case '1': case '2': case '3': case '4':
+         case '5': case '6': case '7': case '8': case '9':
+            c -= '0'; break;
+         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+            c -= ('a' - 10); break;
+         case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+            c -= ('A' - 10); break;
+         case _rJSON_EOF:
+            _rjson_error(json, "unterminated string literal in Unicode");
+            return (unsigned int)-1;
+         default:
+            _rjson_error_char(json, "invalid Unicode escape hexadecimal %s", c);
+            return (unsigned int)-1;
+      }
+      shift -= 4;
+      cp |= ((unsigned int)c << shift);
+      if (!shift) return cp;
+   }
+}
+
+static bool _rjson_read_unicode(rjson_t *json)
+{
+   #define _rJSON_READ_UNICODE_REPLACE_OR_IGNORE \
+      if (json->option_flags & (RJSON_OPTION_IGNORE_INVALID_ENCODING \
+            | RJSON_OPTION_REPLACE_INVALID_ENCODING)) goto replace_or_ignore;
+
+   unsigned int cp;
+
+   if ((cp = _rjson_get_unicode_cp(json)) == (unsigned int)-1)
+      return false;
+
+   if (cp >= 0xd800 && cp <= 0xdbff)
+   {
+      /* This is the high portion of a surrogate pair; we need to read the
+       * lower portion to get the codepoint */
+      unsigned int l, h = cp;
+
+      _rjson_char_t c = _rjson_char_get(json);
+      if (c == _rJSON_EOF)
+      {
+         _rjson_error(json, "unterminated string literal in Unicode");
+         return false;
+      }
+      if (c != '\\')
+      {
+         _rjson_error_char(json, "invalid continuation %s"
+               " for surrogate pair, expected '\\'", c);
+         return false;
+      }
+
+      c = _rjson_char_get(json);
+      if (c == _rJSON_EOF)
+      {
+         _rjson_error(json, "unterminated string literal in Unicode");
+         return false;
+      }
+      if (c != 'u')
+      {
+         _rjson_error_char(json, "invalid continuation %s"
+               " for surrogate pair, expected 'u'", c);
+         return false;
+      }
+      if ((l = _rjson_get_unicode_cp(json)) == (unsigned int)-1)
+         return false;
+      if (l < 0xdc00 || l > 0xdfff)
+      {
+         _rJSON_READ_UNICODE_REPLACE_OR_IGNORE
+         _rjson_error(json, "surrogate pair continuation \\u%04x out "
+            "of range (dc00-dfff)", l);
+         return false;
+      }
+      cp = ((h - 0xd800) * 0x400) + ((l - 0xdc00) + 0x10000);
+   }
+   else if (cp >= 0xdc00 && cp <= 0xdfff)
+   {
+      _rJSON_READ_UNICODE_REPLACE_OR_IGNORE
+      _rjson_error(json, "dangling surrogate \\u%04x", cp);
+      return false;
+   }
+
+   if (cp < 0x80UL)
+      return _rjson_pushchar(json, cp);
+
+   if (cp < 0x0800UL)
+      return (_rjson_pushchar(json, (cp >> 6 & 0x1F) | 0xC0) &&
+              _rjson_pushchar(json, (cp >> 0 & 0x3F) | 0x80));
+
+   if (cp < 0x010000UL)
+   {
+      if (cp >= 0xd800 && cp <= 0xdfff)
+      {
+         _rJSON_READ_UNICODE_REPLACE_OR_IGNORE
+         _rjson_error(json, "invalid codepoint %04x", cp);
+         return false;
+      }
+      return (_rjson_pushchar(json, (cp >> 12 & 0x0F) | 0xE0) &&
+              _rjson_pushchar(json, (cp >>  6 & 0x3F) | 0x80) &&
+              _rjson_pushchar(json, (cp >>  0 & 0x3F) | 0x80));
+   }
+   if (cp < 0x110000UL)
+      return (_rjson_pushchar(json, (cp >> 18 & 0x07) | 0xF0) &&
+              _rjson_pushchar(json, (cp >> 12 & 0x3F) | 0x80) &&
+              _rjson_pushchar(json, (cp >>  6 & 0x3F) | 0x80) &&
+              _rjson_pushchar(json, (cp >>  0 & 0x3F) | 0x80));
+
+   _rJSON_READ_UNICODE_REPLACE_OR_IGNORE
+   _rjson_error(json, "unable to encode %04x as UTF-8", cp);
+   return false;
+   replace_or_ignore:
+   return ((json->option_flags & RJSON_OPTION_IGNORE_INVALID_ENCODING) ||
+         _rjson_pushchar(json, '?'));
+   #undef _rJSON_READ_UNICODE_REPLACE_OR_IGNORE
+}
+
+static bool _rjson_validate_utf8(rjson_t *json)
+{
+   unsigned char first, c;
+   unsigned char *p;
+   unsigned char *from = (unsigned char *)
+         (json->string_pass_through ? json->string_pass_through : json->string);
+   unsigned char *to = from + json->string_len;
+
+   if (json->option_flags & RJSON_OPTION_IGNORE_INVALID_ENCODING)
+      return true;
+
+   for (;;)
+   {
+      if (from == to) return true;
+      first = *from;
+      if (first <= 0x7F) { from++; continue; } /* ascii */
+      p = from;
+      if (first <= 0xC1)
+      {
+         /* continuation or overlong encoding of an ASCII byte */
+         goto invalid_utf8;
+      }
+      if (first <= 0xDF)
+      {
+         if ((from = p + 2) > to) goto invalid_utf8;
+         continue_length_2:
+         c = p[1];
+         switch (first)
+         {
+            case 0xE0: c = (c < 0xA0 || c > 0xBF); break;
+            case 0xED: c = (c < 0x80 || c > 0x9F); break;
+            case 0xF0: c = (c < 0x90 || c > 0xBF); break;
+            case 0xF4: c = (c < 0x80 || c > 0x8F); break;
+            default:   c = (c < 0x80 || c > 0xBF); break;
+         }
+         if (c) goto invalid_utf8;
+      }
+      else if (first <= 0xEF)
+      {
+         if ((from = p + 3) > to) goto invalid_utf8;
+         from = p + 3;
+         continue_length_3:
+         if ((c = p[2]) < 0x80 || c > 0xBF) goto invalid_utf8;
+         goto continue_length_2;
+      }
+      else if (first <= 0xF4)
+      {
+         if ((from = p + 4) > to) goto invalid_utf8;
+         if ((c = p[3]) < 0x80 || c > 0xBF) goto invalid_utf8;
+         goto continue_length_3;
+      }
+      else goto invalid_utf8; /* length 5 or 6 or invalid UTF-8 */
+      continue;
+      invalid_utf8:
+      if (!(json->option_flags & RJSON_OPTION_REPLACE_INVALID_ENCODING))
+      {
+         _rjson_error(json, "invalid UTF-8 character in string");
+         return false;
+      }
+      from = p;
+      *from++ = '?';
+      while (from != to && (*from & 0x80)) *from++ = '?';
+   }
+}
+
+static enum rjson_type _rjson_read_string(rjson_t *json)
+{
+   const unsigned char *p   = json->input_p, *raw = p;
+   const unsigned char *end = json->input_end;
+   unsigned char utf8mask = 0;
+   json->string_pass_through = NULL;
+   json->string_len = 0;
+   for (;;)
+   {
+      if (_rJSON_LIKELY(p != end))
+      {
+         unsigned char c = *p;
+         if (_rJSON_LIKELY(c != '"' && c != '\\' && c >= 0x20))
+         {
+            /* handle most common case first, it's faster */
+            utf8mask |= c;
+            p++;
+         }
+         else if (c == '"')
+         {
+            json->input_p = p + 1;
+            if (json->string_len == 0 && p + 1 != end)
+            {
+               /* raw string fully inside input buffer, pass through */
+               json->string_len = p - raw;
+               json->string_pass_through = (char*)raw;
+            }
+            else if (raw != p && !_rjson_pushchars(json, raw, p))
+            {
+               /* out of memory */
+               return RJSON_ERROR;
+            }
+            if ((utf8mask & 0x80) && !_rjson_validate_utf8(json))
+            {
+               /* contains invalid UTF-8 byte sequences */
+               return RJSON_ERROR;
+            }
+            return RJSON_STRING;
+         }
+         else if (c == '\\')
+         {
+            _rjson_char_t esc;
+            if (raw != p)
+            {
+               /* can't pass through string with escapes, use string buffer */
+               if (!_rjson_pushchars(json, raw, p)) return RJSON_ERROR;
+            }
+            json->input_p = p + 1;
+            esc = _rjson_char_get(json);
+            switch (esc)
+            {
+               case 'u':
+                  if (!_rjson_read_unicode(json))
+                     return RJSON_ERROR;
+                  break;
+
+               case 'b': esc = '\b'; goto escape_pushchar;
+               case 'f': esc = '\f'; goto escape_pushchar;
+               case 'n': esc = '\n'; goto escape_pushchar;
+               case 'r': 
+                  if (!(json->option_flags & RJSON_OPTION_IGNORE_STRING_CARRIAGE_RETURN))
+                  {
+                     esc = '\r';
+                     goto escape_pushchar;
+                  }
+                  break;
+               case 't': esc = '\t'; goto escape_pushchar;
+
+               case '/': case '"': case '\\':
+                  escape_pushchar:
+                  if (!_rjson_pushchar(json, esc))
+                     return RJSON_ERROR;
+                  break;
+
+               case _rJSON_EOF:
+                  return _rjson_error(json, "unterminated string literal in escape");
+
+               default:
+                  return _rjson_error_char(json, "invalid escaped %s", esc);
+            }
+            raw = p = json->input_p;
+            end     = json->input_end;
+         }
+         else if (!(json->option_flags & RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS))
+            return _rjson_error_char(json, "unescaped control character %s in string", c);
+         else
+            p++;
+      }
+      else
+      {
+         if (raw != p)
+         {
+            /* not fully inside input buffer, copy to string buffer */
+            if (!_rjson_pushchars(json, raw, p)) return RJSON_ERROR;
+         }
+         if (!_rjson_io_input(json))
+            return _rjson_error(json, "unterminated string literal");
+         raw = p = json->input_p;
+         end     = json->input_end;
+      }
+   }
+}
+
+static enum rjson_type _rjson_read_number(rjson_t *json)
+{
+   const unsigned char *p     = json->input_p - 1;
+   const unsigned char *end   = json->input_end;
+   const unsigned char *start = p;
+
+   json->string_len = 0;
+   json->string_pass_through = NULL;
+   for (;;)
+   {
+      if (_rJSON_LIKELY(p != end))
+      {
+         switch (*p++)
+         {
+            case '+': case '-': case '.':
+            case '0': case '1': case '2': case '3': case '4':
+            case '5': case '6': case '7': case '8': case '9':
+            case 'E': case 'e':
+               continue;
+         }
+         p--;
+         json->input_p = p;
+         if (!_rjson_pushchars(json, start, p))
+            return RJSON_ERROR; /* out of memory */
+         break;
+      }
+      else
+      {
+         /* number sequences are always copied to the string buffer */
+         if (!_rjson_pushchars(json, start, p)) return RJSON_ERROR;
+         if (!_rjson_io_input(json))
+         {
+            /* EOF here is not an error for a number */
+            json->input_p = json->input_end;
+            break;
+         }
+         start = p = json->input_p;
+         end = json->input_end;
+      }
+   }
+
+   p = (const unsigned char *)json->string;
+   end = (p + json->string_len);
+
+   /* validate json number */
+   if (*p == '-' && ++p == end) goto invalid_number;
+   if (*p == '0')
+   {
+      if (++p == end) return RJSON_NUMBER;
+   }
+   else
+   {
+      if (*p < '1' || *p > '9') goto invalid_number;
+      do { if (++p == end) return RJSON_NUMBER; }
+      while (*p >= '0' && *p <= '9');
+   }
+   if (*p == '.')
+   {
+      if (++p == end) goto invalid_number;
+      if (*p < '0' || *p > '9') goto invalid_number;
+      do { if (++p == end) return RJSON_NUMBER; }
+      while (*p >= '0' && *p <= '9');
+   }
+   if (((*p)|0x20) == 'e')
+   {
+      if (++p == end) goto invalid_number;
+      if ((*p == '-' || *p == '+') && ++p == end) goto invalid_number;
+      if (*p < '0' || *p > '9') goto invalid_number;
+      do { if (++p == end) return RJSON_NUMBER; }
+      while (*p >= '0' && *p <= '9');
+   }
+   invalid_number:
+   return _rjson_error_char(json, "unexpected %s in number",
+         (p == json->input_end ? _rJSON_EOF : p[p == end ? -1 : 0]));
+}
+
+static enum rjson_type _rjson_push_stack(rjson_t *json, enum _rjson_token t)
+{
+   if (json->stack_top + 1 == json->stack + json->stack_cap)
+   {
+      /* reached allocated stack size, either reallocate or abort */
+      unsigned int new_stack_cap;
+      struct _rjson_stack *new_stack;
+      size_t stack_alloc;
+      if (json->stack_cap == json->stack_max)
+         return _rjson_error(json, "maximum depth of nesting reached");
+
+      new_stack_cap = json->stack_cap + 4;
+      if (new_stack_cap > json->stack_max)
+         new_stack_cap = json->stack_max;
+      stack_alloc = new_stack_cap * sizeof(struct _rjson_stack);
+      if (json->stack != json->inline_stack)
+         new_stack = (struct _rjson_stack *)realloc(json->stack, stack_alloc);
+      else if ((new_stack = (struct _rjson_stack*)malloc(stack_alloc)) != NULL)
+         memcpy(new_stack, json->inline_stack, sizeof(json->inline_stack));
+      if (new_stack == NULL)
+         return _rjson_error(json, "out of memory");
+
+      json->stack = new_stack;
+      json->stack_top = new_stack + json->stack_cap - 1;
+      json->stack_cap = new_stack_cap;
+   }
+   json->stack_top++;
+   json->stack_top->count = 0;
+   return (json->stack_top->type =
+            (t == _rJSON_TOK_ARRAY ? RJSON_ARRAY : RJSON_OBJECT));
+}
+
+static enum rjson_type _rjson_read_name(rjson_t *json, const char *pattern, enum rjson_type type)
+{
+   _rjson_char_t c;
+   const char *p;
+   for (p = pattern; *p; p++)
+      if (*p != (c = _rjson_char_get(json)))
+         return _rjson_error_char(json, "unexpected %s in value", c);
+   return type;
+}
+
+static bool _rjson_optional_skip(rjson_t *json, const unsigned char **p, const unsigned char **end)
+{
+   unsigned char c, skip = (*p)[-1];
+   int state = 0;
+
+   if (skip == '/' && !(json->option_flags & RJSON_OPTION_ALLOW_COMMENTS))
+      return false;
+
+   if (skip == 0xEF && (!(json->option_flags & RJSON_OPTION_ALLOW_UTF8BOM) ||
+         json->source_line != 1 || json->source_column_p != json->input_p))
+      return false;
+
+   for (;;)
+   {
+      if (*p == *end)
+      {
+         if (!_rjson_io_input(json))
+         {
+            _rjson_error(json, "unfinished %s",
+                  (skip == '/' ? "comment" : "utf8 byte order mark"));
+            return false;
+         }
+         *p = json->input_p, *end = json->input_end;
+      }
+      c = *(*p)++;
+      if (skip == '/')
+      {
+         if      (state == 0 && c == '/') state = 1;
+         else if (state == 0 && c == '*') state = 2;
+         else if (state == 0) return false;
+         else if (state == 1 && c == '\n') return true;
+         else if (state == 2 && c == '*') state = 3;
+         else if (state == 3 && c == '/') return true;
+         else if (state == 3 && c != '*') state = 2;
+      }
+      else if (skip == 0xEF)
+      {
+         if      (state == 0 && c == 0xBB) state = 1;
+         else if (state == 1 && c == 0xBF) return true;
+         else return false;
+      }
+   }
+   return false;
+}
+
+enum rjson_type rjson_next(rjson_t *json)
+{
+   struct _rjson_stack *stack = json->stack_top;
+   const unsigned char *p   = json->input_p;
+   const unsigned char *end = json->input_end;
+   unsigned char tok;
+   unsigned char passed_token = false;
+
+   /* JSON token look-up-table */
+   static const unsigned char token_lut[256] =
+   {
+      #define i _rJSON_TOK_ERROR
+      /*   0 | 0x00 |   */ i,i,i,i,i,i,i,i,i,
+      /*   9 | 0x09 |\t */ _rJSON_TOK_WHITESPACE,
+      /*  10 | 0x0A |\n */ _rJSON_TOK_NEWLINE, i,i,
+      /*  13 | 0x0D |\r */ _rJSON_TOK_WHITESPACE, i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,
+      /*  32 | 0x20 |   */ _rJSON_TOK_WHITESPACE, i,
+      /*  34 | 0x22 | " */ _rJSON_TOK_STRING, i,i,i,i,i,i,i,i,i,
+      /*  44 | 0x2C | , */ _rJSON_TOK_COMMA,
+      /*  45 | 0x2D | - */ _rJSON_TOK_NUMBER, i,
+      /*  47 | 0x2F | / */ _rJSON_TOK_OPTIONAL_SKIP,
+      /*  48 | 0x30 | 0 */ _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER,
+      /*  53 | 0x35 | 5 */ _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER, _rJSON_TOK_NUMBER,
+      /*  58 | 0x3A | : */ _rJSON_TOK_COLON, i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,
+      /*  91 | 0x5B | [ */ _rJSON_TOK_ARRAY, i,
+      /*  93 | 0x5D | ] */ _rJSON_TOK_ARRAY_END, i,i,i,i,i,i,i,i,
+      /* 102 | 0x66 | f */ _rJSON_TOK_FALSE, i,i,i,i,i,i,i,
+      /* 110 | 0x6E | n */ _rJSON_TOK_NULL, i,i,i,i,i,
+      /* 116 | 0x74 | t */ _rJSON_TOK_TRUE, i,i,i,i,i,i,
+      /* 123 | 0x7B | { */ _rJSON_TOK_OBJECT, i,
+      /* 125 | 0x7D | } */ _rJSON_TOK_OBJECT_END,
+      /* 126 | 0x7E | ~ */ i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,
+      /* 164 | 0xA4 |   */ i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,
+      /* 202 | 0xCA |   */ i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,
+      /* 239 | 0xEF |   */ _rJSON_TOK_OPTIONAL_SKIP, i,i,i,i,i,i,i,i,i,i,i,i,i,i,i,i
+      #undef i
+   };
+
+   if (_rJSON_LIKELY(stack->type != RJSON_ERROR))
+   {
+      for (;;)
+      {
+         if (_rJSON_LIKELY(p != end))
+         {
+            tok = token_lut[*p++];
+            if (_rJSON_LIKELY(tok > _rJSON_TOK_OPTIONAL_SKIP))
+            {
+               /* actual JSON token, process below */
+            }
+            else if (_rJSON_LIKELY(tok == _rJSON_TOK_WHITESPACE))
+            {
+               continue;
+            }
+            else if (tok == _rJSON_TOK_NEWLINE)
+            {
+               json->source_line++;
+               json->source_column_p = p;
+               continue;
+            }
+            else if (tok == _rJSON_TOK_OPTIONAL_SKIP)
+            {
+               if (_rjson_optional_skip(json, &p, &end))
+                  continue;
+            }
+         }
+         else if (_rJSON_LIKELY(_rjson_io_input(json)))
+         {
+            p = json->input_p, end = json->input_end;
+            continue;
+         }
+         else
+         {
+            p = json->input_end;
+            tok = _rJSON_TOK_EOF;
+         }
+
+         if (stack->type == RJSON_OBJECT)
+         {
+            if (stack->count & 1)
+            {
+               /* Expecting colon followed by value. */
+               if (passed_token)
+                  goto read_value;
+               if (_rJSON_LIKELY(tok == _rJSON_TOK_COLON))
+               {
+                  passed_token = true;
+                  continue;
+               }
+               json->input_p = p;
+               return _rjson_error_token(json, "expected ':' not %s after member name", tok);
+            }
+            if (passed_token)
+            {
+               if (_rJSON_LIKELY(tok == _rJSON_TOK_STRING))
+                  goto read_value;
+               json->input_p = p;
+               return _rjson_error(json, "expected member name after ','");
+            }
+            if (tok == _rJSON_TOK_OBJECT_END)
+            {
+               json->input_p = p;
+               json->stack_top--;
+               return RJSON_OBJECT_END;
+            }
+            if (stack->count == 0)
+            {
+               /* No member name/value pairs yet. */
+               if (_rJSON_LIKELY(tok == _rJSON_TOK_STRING))
+                  goto read_value;
+               json->input_p = p;
+               return _rjson_error(json, "expected member name or '}'");
+            }
+            /* Expecting comma followed by member name. */
+            if (_rJSON_LIKELY(tok == _rJSON_TOK_COMMA))
+            {
+               passed_token = true;
+               continue;
+            }
+            json->input_p = p;
+            return _rjson_error_token(json, "expected ',' or '}' not %s after member value", tok);
+         }
+         else if (stack->type == RJSON_ARRAY)
+         {
+            if (passed_token)
+               goto read_value;
+            if (tok == _rJSON_TOK_ARRAY_END)
+            {
+               json->input_p = p;
+               json->stack_top--;
+               return RJSON_ARRAY_END;
+            }
+            if (stack->count == 0)
+               goto read_value;
+            if (_rJSON_LIKELY(tok == _rJSON_TOK_COMMA))
+            {
+               passed_token = true;
+               continue;
+            }
+            json->input_p = p;
+            return _rjson_error_token(json, "expected ',' or ']' not %s in array", tok);
+         }
+         else
+         {
+            if (_rJSON_LIKELY(!stack->count && tok != _rJSON_TOK_EOF))
+               goto read_value;
+            json->input_p = p;
+            if (!stack->count)
+               return _rjson_error(json, "reached end without any data");
+            if (tok == _rJSON_TOK_EOF)
+               return RJSON_DONE;
+            if (!(json->option_flags & RJSON_OPTION_ALLOW_TRAILING_DATA))
+               return _rjson_error_token(json, "expected end of stream instead of %s", tok);
+            json->input_p--;
+            return RJSON_DONE;
+         }
+
+         /* read value for current token */
+         read_value:
+         json->input_p = p;
+         stack->count++;
+         /* This is optimal when there are many strings, otherwise a switch statement
+          * or a function pointer table is better (depending on compiler/cpu) */
+         if      ((tok == _rJSON_TOK_STRING))
+            return _rjson_read_string(json);
+         else if ((tok == _rJSON_TOK_NUMBER))
+            return _rjson_read_number(json);
+         else if ((tok == _rJSON_TOK_OBJECT))
+            return _rjson_push_stack(json, _rJSON_TOK_OBJECT);
+         else if ((tok == _rJSON_TOK_ARRAY))
+            return _rjson_push_stack(json, _rJSON_TOK_ARRAY);
+         else if ((tok == _rJSON_TOK_TRUE))
+            return _rjson_read_name(json, "rue", RJSON_TRUE);
+         else if ((tok == _rJSON_TOK_FALSE))
+            return _rjson_read_name(json, "alse", RJSON_FALSE);
+         else if ((tok == _rJSON_TOK_NULL))
+            return _rjson_read_name(json, "ull", RJSON_NULL);
+         else return _rjson_error_token(json, "unexpected %s in value", tok);
+      }
+   }
+   return RJSON_ERROR;
+}
+
+void _rjson_setup(rjson_t *json, rjson_io_t io, void *user_data, int input_len)
+{
+   json->io = io;
+   json->user_data = user_data;
+   json->input_len = input_len;
+   json->input_p = json->input_end = json->input_buf + input_len;
+
+   json->stack = json->inline_stack;
+   json->stack_top = json->stack;
+   json->stack_top->type = RJSON_DONE;
+   json->stack_top->count = 0;
+   json->stack_cap = (unsigned int)(sizeof(json->inline_stack) / sizeof(json->inline_stack[0]));
+   json->stack_max = (unsigned int)50;
+
+   json->string = json->inline_string;
+   json->string_pass_through = NULL;
+   json->string_len = 0;
+   json->string_cap = sizeof(json->inline_string);
+
+   json->source_line = 1;
+   json->source_column_p = json->input_p;
+   json->option_flags = 0;
+   json->decimal_sep = 0;
+}
+
+rjson_t *rjson_open_user(rjson_io_t io, void *user_data, int io_block_size)
+{
+   rjson_t* json = (rjson_t*)malloc(
+         sizeof(rjson_t) - sizeof(((rjson_t*)0)->input_buf) + io_block_size);
+   if (json) _rjson_setup(json, io, user_data, io_block_size);
+   return json;
+}
+
+static int _rjson_buffer_io(void* buf, int len, void *user)
+{
+   const char **ud = (const char **)user;
+   if (ud[1] - ud[0] < len) len = (int)(ud[1] - ud[0]);
+   memcpy(buf, ud[0], len);
+   ud[0] += len;
+   return len;
+}
+
+rjson_t *rjson_open_buffer(const void *buffer, size_t size)
+{
+   rjson_t *json = (rjson_t *)malloc(sizeof(rjson_t) + sizeof(const char *)*2);
+   const char **ud = (const char **)(json + 1);
+   if (!json) return NULL;
+   ud[0] = (const char *)buffer;
+   ud[1] = ud[0] + size;
+   _rjson_setup(json, _rjson_buffer_io, (void*)ud, sizeof(json->input_buf));
+   return json;
+}
+
+rjson_t *rjson_open_string(const char *string)
+{
+   return rjson_open_buffer(string, strlen(string));
+}
+
+static int _rjson_stream_io(void* buf, int len, void *user)
+{
+   return (int)intfstream_read((intfstream_t*)user, buf, (uint64_t)len);
+}
+
+rjson_t *rjson_open_stream(struct intfstream_internal *stream)
+{
+   /* Allocate an input buffer based on the file size */
+   int64_t size = intfstream_get_size(stream);
+   int io_size =
+         (size > 1024*1024 ? 4096 :
+         (size >  256*1024 ? 2048 : 1024));
+   return rjson_open_user(_rjson_stream_io, stream, io_size);
+}
+
+static int _rjson_rfile_io(void* buf, int len, void *user)
+{
+   return (int)filestream_read((RFILE*)user, buf, (int64_t)len);
+}
+
+rjson_t *rjson_open_rfile(RFILE *rfile)
+{
+   /* Allocate an input buffer based on the file size */
+   int64_t size = filestream_get_size(rfile);
+   int io_size =
+         (size > 1024*1024 ? 4096 :
+         (size >  256*1024 ? 2048 : 1024));
+   return rjson_open_user(_rjson_rfile_io, rfile, io_size);
+}
+
+void rjson_set_options(rjson_t *json, char rjson_option_flags)
+{
+   json->option_flags = rjson_option_flags;
+}
+
+void rjson_set_max_depth(rjson_t *json, unsigned int max_depth)
+{
+   json->stack_max = max_depth;
+}
+
+const char *rjson_get_string(rjson_t *json, size_t *length)
+{
+   char* str = (json->string_pass_through ? json->string_pass_through : json->string);
+   if (length != NULL)
+      *length = json->string_len;
+   str[json->string_len] = '\0';
+   return str;
+}
+
+double rjson_get_double(rjson_t *json)
+{
+   char* str = (json->string_pass_through ? json->string_pass_through : json->string);
+   str[json->string_len] = '\0';
+   if (json->decimal_sep != '.')
+   {
+      /* handle locale that uses a non-standard decimal separator */
+      char *p;
+      if (json->decimal_sep == 0)
+      {
+         char test[4];
+         snprintf(test, sizeof(test), "%.1f", 0.0f);
+         json->decimal_sep = test[1];
+      }
+      if (json->decimal_sep != '.' && (p = strchr(str, '.')) != NULL)
+      {
+         double res;
+         *p = json->decimal_sep;
+         res = atof(str);
+         *p = '.';
+         return res;
+      }
+   }
+   return atof(str);
+}
+
+int rjson_get_int(rjson_t *json)
+{
+   char* str = (json->string_pass_through ? json->string_pass_through : json->string);
+   str[json->string_len] = '\0';
+   return atoi(str);
+}
+
+const char *rjson_get_error(rjson_t *json)
+{
+   return (json->stack_top->type == RJSON_ERROR ? json->error_text : "");
+}
+
+void rjson_set_error(rjson_t *json, const char* error)
+{
+   _rjson_error(json, "%s", error);
+}
+
+size_t rjson_get_source_line(rjson_t *json)
+{
+   return json->source_line;
+}
+
+size_t rjson_get_source_column(rjson_t *json)
+{
+   return (json->input_p == json->source_column_p ? 1 :
+         json->input_p - json->source_column_p);
+}
+
+int rjson_get_source_context_len(rjson_t *json)
+{
+   const unsigned char *from = json->input_buf, *to = json->input_end, *p = json->input_p;
+   return ((p + 256 < to ? p + 256 : to) - (p > from + 256 ? p - 256 : from));
+}
+
+const char* rjson_get_source_context_buf(rjson_t *json)
+{
+   /* inside the input buffer, some " may have been replaced with \0. */
+   const unsigned char *p = json->input_p, *from = json->input_buf;
+   unsigned char *i = json->input_buf;
+   for (; i != json->input_end; i++) if (*i == '\0') *i = '"';
+   return (const char*)(p > from + 256 ? p - 256 : from);
+}
+
+bool rjson_check_context(rjson_t *json, unsigned int depth, ...)
+{
+   va_list ap;
+   const struct _rjson_stack *stack = json->stack, *stack_top = json->stack_top;
+   if (stack_top - stack != depth)
+      return false;
+   va_start(ap, depth);
+   while (++stack <= stack_top)
+   {
+      if (va_arg(ap, enum rjson_type) == stack->type) continue;
+      va_end(ap);
+      return false;
+   }
+   va_end(ap);
+   return true;
+}
+
+unsigned int rjson_get_context_depth(rjson_t *json)
+{
+   return json->stack_top - json->stack;
+}
+
+size_t rjson_get_context_count(rjson_t *json)
+{
+   return json->stack_top->count;
+}
+
+enum rjson_type rjson_get_context_type(rjson_t *json)
+{
+   return json->stack_top->type;
+}
+
+void rjson_free(rjson_t *json)
+{
+   if (json->stack != json->inline_stack)
+      free(json->stack);
+   if (json->string != json->inline_string)
+      free(json->string);
+   free(json);
+}
+
+static bool _rjson_nop_default(void *context) { return true; }
+static bool _rjson_nop_string(void *context, const char *value, size_t length) { return true; }
+static bool _rjson_nop_bool(void *context, bool value) { return true; }
+
+enum rjson_type rjson_parse(rjson_t *json, void* context,
+      bool (*object_member_handler)(void *context, const char *str, size_t len),
+      bool (*string_handler       )(void *context, const char *str, size_t len),
+      bool (*number_handler       )(void *context, const char *str, size_t len),
+      bool (*start_object_handler )(void *context),
+      bool (*end_object_handler   )(void *context),
+      bool (*start_array_handler  )(void *context),
+      bool (*end_array_handler    )(void *context),
+      bool (*boolean_handler      )(void *context, bool value),
+      bool (*null_handler         )(void *context))
+{
+   bool in_object = false;
+   size_t len;
+   const char* string;
+   if (!object_member_handler) object_member_handler = _rjson_nop_string;
+   if (!string_handler       ) string_handler        = _rjson_nop_string;
+   if (!number_handler       ) number_handler        = _rjson_nop_string;
+   if (!start_object_handler ) start_object_handler  = _rjson_nop_default;
+   if (!end_object_handler   ) end_object_handler    = _rjson_nop_default;
+   if (!start_array_handler  ) start_array_handler   = _rjson_nop_default;
+   if (!end_array_handler    ) end_array_handler     = _rjson_nop_default;
+   if (!boolean_handler      ) boolean_handler       = _rjson_nop_bool;
+   if (!null_handler         ) null_handler          = _rjson_nop_default;
+   for (;;)
+   {
+      switch (rjson_next(json))
+      {
+         case RJSON_STRING:
+            string = rjson_get_string(json, &len);
+            if (_rJSON_LIKELY(
+                  (in_object && (json->stack_top->count & 1) ?
+                     object_member_handler : string_handler)
+                     (context, string, len)))
+               continue;
+            return RJSON_STRING;
+         case RJSON_NUMBER:
+            string = rjson_get_string(json, &len);
+            if (_rJSON_LIKELY(number_handler(context, string, len)))
+               continue;
+            return RJSON_NUMBER;
+         case RJSON_OBJECT:
+            in_object = true;
+            if (_rJSON_LIKELY(start_object_handler(context)))
+               continue;
+            return RJSON_OBJECT;
+         case RJSON_ARRAY:
+            in_object = false;
+            if (_rJSON_LIKELY(start_array_handler(context)))
+               continue;
+            return RJSON_ARRAY;
+         case RJSON_OBJECT_END:
+            if (_rJSON_LIKELY(end_object_handler(context)))
+            {
+               in_object = (json->stack_top->type == RJSON_OBJECT);
+               continue;
+            }
+            return RJSON_OBJECT_END;
+         case RJSON_ARRAY_END:
+            if (_rJSON_LIKELY(end_array_handler(context)))
+            {
+               in_object = (json->stack_top->type == RJSON_OBJECT);
+               continue;
+            }
+            return RJSON_ARRAY_END;
+         case RJSON_TRUE:
+            if (_rJSON_LIKELY(boolean_handler(context, true)))
+               continue;
+            return RJSON_TRUE;
+         case RJSON_FALSE:
+            if (_rJSON_LIKELY(boolean_handler(context, false)))
+               continue;
+            return RJSON_FALSE;
+         case RJSON_NULL:
+            if (_rJSON_LIKELY(null_handler(context)))
+               continue;
+            return RJSON_NULL;
+         case RJSON_ERROR:
+            return RJSON_ERROR;
+         case RJSON_DONE:
+            return RJSON_DONE;
+      }
+   }
+}
+
+bool rjson_parse_quick(const char *string, void* context, char option_flags,
+      bool (*object_member_handler)(void *context, const char *str, size_t len),
+      bool (*string_handler       )(void *context, const char *str, size_t len),
+      bool (*number_handler       )(void *context, const char *str, size_t len),
+      bool (*start_object_handler )(void *context),
+      bool (*end_object_handler   )(void *context),
+      bool (*start_array_handler  )(void *context),
+      bool (*end_array_handler    )(void *context),
+      bool (*boolean_handler      )(void *context, bool value),
+      bool (*null_handler         )(void *context),
+      void (*error_handler        )(void *context, int line, int col, const char* error))
+{
+   const char *user_data[2];
+   rjson_t json;
+   user_data[0] = string;
+   user_data[1] = string + strlen(string);
+   _rjson_setup(&json, _rjson_buffer_io, (void*)user_data, sizeof(json.input_buf));
+   rjson_set_options(&json, option_flags);
+   if (rjson_parse(&json, context,
+         object_member_handler, string_handler, number_handler,
+         start_object_handler, end_object_handler,
+         start_array_handler, end_array_handler,
+         boolean_handler, null_handler) == RJSON_DONE)
+      return true;
+   if (error_handler)
+      error_handler(context,
+            (int)rjson_get_source_line(&json),
+            (int)rjson_get_source_column(&json),
+            rjson_get_error(&json));
+   return false;
+}
+
+struct rjsonwriter
+{
+   char* buf;
+   int buf_num, buf_cap;
+
+   rjsonwriter_io_t io;
+   void *user_data;
+
+   char option_flags;
+   char decimal_sep;
+   char* error_text;
+
+   char inline_buf[1024];
+};
+
+rjsonwriter_t *rjsonwriter_open_user(rjsonwriter_io_t io, void *user_data)
+{
+   rjsonwriter_t* writer = (rjsonwriter_t*)malloc(sizeof(rjsonwriter_t));
+   if (!writer) return NULL;
+
+   writer->buf = writer->inline_buf;
+   writer->buf_num = 0;
+   writer->buf_cap = sizeof(writer->inline_buf);
+
+   writer->option_flags = 0;
+   writer->decimal_sep = 0;
+   writer->error_text = NULL;
+
+   writer->io = io;
+   writer->user_data = user_data;
+
+   return writer;
+}
+
+static int _rjsonwriter_stream_io(const void* buf, int len, void *user)
+{
+   return (int)intfstream_write((intfstream_t*)user, buf, (uint64_t)len);
+}
+
+rjsonwriter_t *rjsonwriter_open_stream(struct intfstream_internal *stream)
+{
+   return rjsonwriter_open_user(_rjsonwriter_stream_io, stream);
+}
+
+static int _rjsonwriter_rfile_io(const void* buf, int len, void *user)
+{
+   return (int)filestream_write((RFILE*)user, buf, (int64_t)len);
+}
+
+rjsonwriter_t *rjsonwriter_open_rfile(RFILE *rfile)
+{
+   return rjsonwriter_open_user(_rjsonwriter_rfile_io, rfile);
+}
+
+bool rjsonwriter_free(rjsonwriter_t *writer)
+{
+   bool res = rjsonwriter_flush(writer);
+   if (writer->buf != writer->inline_buf)
+      free(writer->buf);
+   free(writer);
+   return res;
+}
+
+void rjsonwriter_set_options(rjsonwriter_t *writer, int rjsonwriter_option_flags)
+{
+   writer->option_flags = rjsonwriter_option_flags;
+}
+
+bool rjsonwriter_flush(rjsonwriter_t *writer)
+{
+   if (writer->buf_num && !writer->error_text && writer->io(writer->buf,
+            writer->buf_num, writer->user_data) != writer->buf_num)
+      writer->error_text = "output error";
+   writer->buf_num = 0;
+   return !writer->error_text;
+}
+
+const char *rjsonwriter_get_error(rjsonwriter_t *writer)
+{
+   return (writer->error_text ? writer->error_text : "");
+}
+
+void rjsonwriter_raw(rjsonwriter_t *writer, const char *buf, int len)
+{
+   if (writer->buf_num + len > writer->buf_cap)
+      rjsonwriter_flush(writer);
+   if (len == 1)
+   {
+      if (buf[0] > ' ' ||
+            !(writer->option_flags & RJSONWRITER_OPTION_SKIP_WHITESPACE))
+         writer->buf[writer->buf_num++] = buf[0];
+      return;
+   }
+   if (writer->buf_num > 0 || len < writer->buf_cap)
+   {
+      unsigned int add = (unsigned int)(writer->buf_cap - writer->buf_num);
+      if (add > (unsigned int)len) add = (unsigned int)len;
+      memcpy(writer->buf + writer->buf_num, buf, add);
+      writer->buf_num += add;
+      if (len == add) return;
+      rjsonwriter_flush(writer);
+      len -= add;
+      buf += add;
+   }
+   if (len < writer->buf_cap)
+   {
+      memcpy(writer->buf, buf, len);
+      writer->buf_num += len;
+   }
+   else writer->io(buf, len, writer->user_data);
+}
+
+void rjsonwriter_rawf(rjsonwriter_t *writer, const char *fmt, ...)
+{
+   int available, need;
+   va_list ap, ap2;
+   if (writer->buf_num >= writer->buf_cap - 16)
+      rjsonwriter_flush(writer);
+   available = (writer->buf_cap - writer->buf_num);
+   va_start(ap, fmt);
+   need = vsnprintf(writer->buf + writer->buf_num, available, fmt, ap);
+   va_end(ap);
+   if (need <= 0) return;
+   if (need < available)
+   {
+      writer->buf_num += need;
+      return;
+   }
+   rjsonwriter_flush(writer);
+   if (need >= writer->buf_cap)
+   {
+      char* newbuf = (char*)malloc(need + 1);
+      if (!newbuf)
+      {
+         if (!writer->error_text) writer->error_text = "out of memory";
+         return;
+      }
+      if (writer->buf != writer->inline_buf)
+         free(writer->buf);
+      writer->buf = newbuf;
+      writer->buf_cap = need + 1;
+   }
+   va_start(ap2, fmt);
+   vsnprintf(writer->buf, writer->buf_cap, fmt, ap2);
+   va_end(ap2);
+   writer->buf_num = need;
+}
+
+void rjsonwriter_add_string(rjsonwriter_t *writer, const char *value)
+{
+   const char *p = (const char*)value, *raw = p;
+   unsigned char c;
+   rjsonwriter_raw(writer, "\"", 1);
+   if (!p) goto string_end;
+   while ((c = (unsigned char)*p++) != '\0')
+   {
+      /* forward slash is special, it should be escaped if the previous character
+       * was a < (intended to avoid having </script> html tags in JSON files) */
+      if (c < 0x20 || c == '\"' || c == '\\' ||
+            (c == '/' && p > value + 1 && p[-2] == '<'))
+      {
+         char* esc, esc_buf[8], esc_len = 2;
+         if (raw != p - 1)
+            rjsonwriter_raw(writer, raw, (int)(p - 1 - raw));
+         switch (c)
+         {
+            case '\b': esc = "\\b"; break;
+            case '\t': esc = "\\t"; break;
+            case '\n': esc = "\\n"; break;
+            case '\f': esc = "\\f"; break;
+            case '\r': esc = "\\r"; break;
+            case '\"': esc = "\\\""; break;
+            case '\\': esc = "\\\\"; break;
+            case '/': esc = "\\/"; break;
+            default:
+               snprintf((esc = esc_buf), sizeof(esc_buf), "\\u%04x", c);
+               esc_len = 6;
+         }
+         rjsonwriter_raw(writer, esc, esc_len);
+         raw = p;
+      }
+   }
+   if (raw != p - 1)
+      rjsonwriter_raw(writer, raw, (int)(p - 1 - raw));
+   string_end:
+   rjsonwriter_raw(writer, "\"", 1);
+}
+
+void rjsonwriter_add_double(rjsonwriter_t *writer, double value)
+{
+   int old_buf_num = writer->buf_num;
+   rjsonwriter_rawf(writer, "%G", value);
+   if (writer->decimal_sep != '.')
+   {
+      /* handle locale that uses a non-standard decimal separator */
+      char *p, *str;
+      if (writer->decimal_sep == 0)
+      {
+         char test[4];
+         snprintf(test, sizeof(test), "%.1f", 0.0f);
+         if ((writer->decimal_sep = test[1]) == '.') return;
+      }
+      str = writer->buf + (old_buf_num > writer->buf_num ? 0 : old_buf_num);
+      if ((p = strchr(str, writer->decimal_sep)) != NULL) *p = '.';
+   }
+}
+
+void rjsonwriter_add_spaces(rjsonwriter_t *writer, int count)
+{
+   int add;
+   if (count <= 0 || (writer->option_flags & RJSONWRITER_OPTION_SKIP_WHITESPACE))
+      return;
+   for (; (add = (count > 8 ? 8 : count)) != 0; count -= add)
+      rjsonwriter_raw(writer, "        ", add);
+}
+
+void rjsonwriter_add_tabs(rjsonwriter_t *writer, int count)
+{
+   int add;
+   if (count <= 0 || (writer->option_flags & RJSONWRITER_OPTION_SKIP_WHITESPACE))
+      return;
+   for (; (add = (count > 8 ? 8 : count)) != 0; count -= add)
+      rjsonwriter_raw(writer, "\t\t\t\t\t\t\t\t", add);
+}
+
+#undef _rJSON_EOF
+#undef _rJSON_LIKELY
diff --git a/libretro-common/include/formats/rjson.h b/libretro-common/include/formats/rjson.h
new file mode 100644
index 0000000000..35d5a8d600
--- /dev/null
+++ b/libretro-common/include/formats/rjson.h
@@ -0,0 +1,263 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rjson.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * 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.
+ */
+
+#ifndef __LIBRETRO_SDK_FORMAT_RJSON_H__
+#define __LIBRETRO_SDK_FORMAT_RJSON_H__
+
+#include <retro_common_api.h>
+#include <retro_inline.h> /* INLINE */
+#include <boolean.h> /* bool */
+#include <stddef.h> /* size_t */
+
+RETRO_BEGIN_DECLS
+
+/* List of possible element types returned by rjson_next */
+enum rjson_type
+{
+   RJSON_DONE,
+   RJSON_OBJECT, RJSON_ARRAY, RJSON_OBJECT_END, RJSON_ARRAY_END,
+   RJSON_STRING, RJSON_NUMBER, RJSON_TRUE, RJSON_FALSE, RJSON_NULL,
+   RJSON_ERROR
+};
+
+/* Options that can be passed to rjson_set_options */
+enum rjson_option
+{
+   /* Allow UTF-8 byte order marks */
+   RJSON_OPTION_ALLOW_UTF8BOM                      = (1<<0),
+   /* Allow JavaScript style comments in the stream */
+   RJSON_OPTION_ALLOW_COMMENTS                     = (1<<1),
+   /* Allow unescaped control characters in strings (bytes 0x00 - 0x1F) */
+   RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS = (1<<2),
+   /* Ignore invalid Unicode escapes and don't validate UTF-8 codes */
+   RJSON_OPTION_IGNORE_INVALID_ENCODING            = (1<<3),
+   /* Replace invalid Unicode escapes and UTF-8 codes with a '?' character */
+   RJSON_OPTION_REPLACE_INVALID_ENCODING           = (1<<4),
+   /* Ignore carriage return (\r escape sequence) in strings */
+   RJSON_OPTION_IGNORE_STRING_CARRIAGE_RETURN      = (1<<5),
+   /* Allow data after the end of the top JSON object/array/value */
+   RJSON_OPTION_ALLOW_TRAILING_DATA                = (1<<6)
+};
+
+/* Custom data input callback
+ * Should return > 0 and <= len on success, 0 on file end and < 0 on error. */
+typedef int (*rjson_io_t)(void* buf, int len, void *user_data);
+typedef struct rjson rjson_t;
+struct intfstream_internal;
+struct RFILE;
+
+/* Create a new parser instance from various sources */
+rjson_t *rjson_open_stream(struct intfstream_internal *stream);
+rjson_t *rjson_open_rfile(struct RFILE *rfile);
+rjson_t *rjson_open_buffer(const void *buffer, size_t size);
+rjson_t *rjson_open_string(const char *string);
+rjson_t *rjson_open_user(rjson_io_t io, void *user_data, int io_block_size);
+
+/* Free the parser instance created with rjson_open_* */
+void rjson_free(rjson_t *json);
+
+/* Set one or more enum rjson_option, will override previously set options.
+ * Use bitwise OR to concatenate multiple options.
+ * By default none of the options are set. */
+void rjson_set_options(rjson_t *json, char rjson_option_flags);
+
+/* Sets the maximum context depth, recursion inside arrays and objects.
+ * By default this is set to 50. */
+void rjson_set_max_depth(rjson_t *json, unsigned int max_depth);
+
+/* Parse to the next JSON element and return the type of it.
+ * Will return RJSON_DONE when successfully reaching the end or
+ * RJSON_ERROR when an error was encountered. */
+enum rjson_type rjson_next(rjson_t *json);
+
+/* Get the current string, null-terminated unescaped UTF-8 encoded.
+ * Can only be used when the current element is RJSON_STRING or RJSON_NUMBER.
+ * The returned pointer is only valid until the parsing continues. */
+const char *rjson_get_string(rjson_t *json, size_t *length);
+
+/* Returns the current number (or string) converted to double or int */
+double rjson_get_double(rjson_t *json);
+int    rjson_get_int(rjson_t *json);
+
+/* Returns a string describing the error once rjson_next/rjson_parse
+ * has returned an unrecoverable RJSON_ERROR (otherwise returns ""). */
+const char *rjson_get_error(rjson_t *json);
+
+/* Can be used to set a custom error description on an invalid JSON structure.
+ * Maximum length of 79 characters and once set the parsing can't continue. */
+void rjson_set_error(rjson_t *json, const char* error);
+
+/* Functions to get the current position in the source stream as well as */
+/* a bit of source json arround the current position for additional detail
+ * when parsing has failed with RJSON_ERROR.
+ * Intended to be used with printf style formatting like:
+ * printf("Invalid JSON at line %d, column %d - %s - Source: ...%.*s...\n",
+ *       (int)rjson_get_source_line(json), (int)rjson_get_source_column(json),
+ *       rjson_get_error(json), rjson_get_source_context_len(json),
+ *       rjson_get_source_context_buf(json)); */
+size_t      rjson_get_source_line(rjson_t *json);
+size_t      rjson_get_source_column(rjson_t *json);
+int         rjson_get_source_context_len(rjson_t *json);
+const char* rjson_get_source_context_buf(rjson_t *json);
+
+/* Confirm the parsing context stack, for example calling
+   rjson_check_context(json, 2, RJSON_OBJECT, RJSON_ARRAY)
+   returns true when inside "{ [ ..." but not for "[ .." or "{ [ { ..." */
+bool rjson_check_context(rjson_t *json, unsigned int depth, ...);
+
+/* Returns the current level of nested objects/arrays */
+unsigned int rjson_get_context_depth(rjson_t *json);
+
+/* Return the current parsing context, that is, RJSON_OBJECT if we are inside
+ * an object, RJSON_ARRAY if we are inside an array, and RJSON_DONE or 
+ * RJSON_ERROR if we are not yet/anymore in either. */
+enum rjson_type rjson_get_context_type(rjson_t *json);
+
+/* While inside an object or an array, this return the number of parsing
+ * events that have already been observed at this level with rjson_next.
+ * In particular, inside an object, an odd number would indicate that the just
+ * observed RJSON_STRING event is a member name. */
+size_t rjson_get_context_count(rjson_t *json);
+
+/* Parse an entire JSON stream with a list of element specific handlers.
+ * Each of the handlers can be passed a function or NULL to ignore it.
+ * If a handler returns false, the parsing will abort and the returned
+ * rjson_type will indicate on which element type parsing was aborted.
+ * Otherwise the return value will be RJSON_DONE or RJSON_ERROR. */
+enum rjson_type rjson_parse(rjson_t *json, void* context,
+      bool (*object_member_handler)(void *context, const char *str, size_t len),
+      bool (*string_handler       )(void *context, const char *str, size_t len),
+      bool (*number_handler       )(void *context, const char *str, size_t len),
+      bool (*start_object_handler )(void *context),
+      bool (*end_object_handler   )(void *context),
+      bool (*start_array_handler  )(void *context),
+      bool (*end_array_handler    )(void *context),
+      bool (*boolean_handler      )(void *context, bool value),
+      bool (*null_handler         )(void *context));
+
+/* A simpler interface to parse a JSON in memory. This will avoid any memory
+ * allocations unless the document contains strings longer than 512 characters.
+ * In the error handler, error will be "" if any of the other handlers aborted. */
+bool rjson_parse_quick(const char *string, void* context, char option_flags,
+      bool (*object_member_handler)(void *context, const char *str, size_t len),
+      bool (*string_handler       )(void *context, const char *str, size_t len),
+      bool (*number_handler       )(void *context, const char *str, size_t len),
+      bool (*start_object_handler )(void *context),
+      bool (*end_object_handler   )(void *context),
+      bool (*start_array_handler  )(void *context),
+      bool (*end_array_handler    )(void *context),
+      bool (*boolean_handler      )(void *context, bool value),
+      bool (*null_handler         )(void *context),
+      void (*error_handler        )(void *context, int line, int col, const char* error));
+
+/* ------------------------------------------------------------------------- */
+
+/* Options that can be passed to rjsonwriter_set_options */
+enum rjsonwriter_option
+{
+   /* Don't write spaces, tabs or newlines to the output (except in strings) */
+   RJSONWRITER_OPTION_SKIP_WHITESPACE = (1<<0)
+};
+
+/* Custom data output callback
+ * Should return len on success and < len on a write error. */
+typedef int (*rjsonwriter_io_t)(const void* buf, int len, void *user_data);
+typedef struct rjsonwriter rjsonwriter_t;
+
+/* Create a new writer instance to various targets */
+rjsonwriter_t *rjsonwriter_open_stream(struct intfstream_internal *stream);
+rjsonwriter_t *rjsonwriter_open_rfile(struct RFILE *rfile);
+rjsonwriter_t *rjsonwriter_open_user(rjsonwriter_io_t io, void *user_data);
+
+/* Free rjsonwriter handle and return result of final rjsonwriter_flush call */
+bool rjsonwriter_free(rjsonwriter_t *writer);
+
+/* Set one or more enum rjsonwriter_option, will override previously set options.
+ * Use bitwise OR to concatenate multiple options.
+ * By default none of the options are set. */
+void rjsonwriter_set_options(rjsonwriter_t *writer, int rjsonwriter_option_flags);
+
+/* Flush any buffered output data to the output stream.
+ * Returns true if the data was successfully written. Once writing fails once,
+ * no more data will be written and flush will always returns false */
+bool rjsonwriter_flush(rjsonwriter_t *writer);
+
+/* Returns a string describing an error or "" if there was none.
+ * The only error possible is "output error" after the io function failed.
+ * If rjsonwriter_rawf were used manually, "out of memory" is also possible. */
+const char *rjsonwriter_get_error(rjsonwriter_t *writer);
+
+/* Used by the inline functions below to append raw data */
+void rjsonwriter_raw(rjsonwriter_t *writer, const char *buf, int len);
+void rjsonwriter_rawf(rjsonwriter_t *writer, const char *fmt, ...);
+
+/* Add a UTF-8 encoded string
+ * Special and control characters are automatically escaped.
+ * If NULL is passed an empty string will be written (not JSON null). */
+void rjsonwriter_add_string(rjsonwriter_t *writer, const char *value);
+
+/* Add a signed or unsigned integer or a double number */
+static INLINE void rjsonwriter_add_int(rjsonwriter_t *writer, int value)
+      { rjsonwriter_rawf(writer, "%d", value); }
+
+static INLINE void rjsonwriter_add_unsigned(rjsonwriter_t *writer, unsigned value)
+      { rjsonwriter_rawf(writer, "%u", value); }
+
+void rjsonwriter_add_double(rjsonwriter_t *writer, double value);
+
+/* Functions to add JSON token characters */
+static INLINE void rjsonwriter_add_start_object(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, "{", 1); }
+
+static INLINE void rjsonwriter_add_end_object(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, "}", 1); }
+
+static INLINE void rjsonwriter_add_start_array(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, "[", 1); }
+
+static INLINE void rjsonwriter_add_end_array(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, "]", 1); }
+
+static INLINE void rjsonwriter_add_colon(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, ":", 1); }
+
+static INLINE void rjsonwriter_add_comma(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, ",", 1); }
+
+/* Functions to add whitespace characters */
+/* These do nothing with the option RJSONWRITER_OPTION_SKIP_WHITESPACE */
+static INLINE void rjsonwriter_add_newline(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, "\n", 1); }
+
+static INLINE void rjsonwriter_add_space(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, " ", 1); }
+
+void rjsonwriter_add_spaces(rjsonwriter_t *writer, int count);
+
+static INLINE void rjsonwriter_add_tab(rjsonwriter_t *writer)
+      { rjsonwriter_raw(writer, "\t", 1); }
+
+void rjsonwriter_add_tabs(rjsonwriter_t *writer, int count);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/network/netplay/netplay_room_parse.c b/network/netplay/netplay_room_parse.c
index 40e526051f..dd710da4f1 100644
--- a/network/netplay/netplay_room_parse.c
+++ b/network/netplay/netplay_room_parse.c
@@ -21,11 +21,11 @@
 #include <string.h>
 #include <string/stdstring.h>
 #include <compat/strl.h>
-#include <formats/jsonsax_full.h>
+#include <formats/rjson.h>
 #include "netplay_discovery.h"
 #include "../../verbosity.h"
 
-enum parse_state
+enum netplay_parse_state
 {
    STATE_START = 0,
    STATE_ARRAY_START,
@@ -41,113 +41,71 @@ struct netplay_rooms
    struct netplay_room *cur;
 };
 
-typedef struct tag_Context
+struct netplay_json_context
 {
-   JSON_Parser parser;
-   char *cur_field;
-   void *cur_member;
-   enum parse_state state;
-} Context;
+   bool *cur_member_bool;
+   int  *cur_member_int;
+   int  *cur_member_inthex;
+   char *cur_member_string;
+   size_t cur_member_size;
+   enum netplay_parse_state state;
+};
 
 /* TODO/FIXME - static global variable */
 static struct netplay_rooms *netplay_rooms_data;
 
-static void parse_context_free(Context* pCtx)
+static bool netplay_json_boolean(void* ctx, bool value)
 {
-   if (pCtx->cur_field)
-      free(pCtx->cur_field);
-
-   pCtx->cur_field = NULL;
-
-   JSON_Parser_Free(pCtx->parser);
-}
-
-static JSON_Parser_HandlerResult JSON_CALL EncodingDetectedHandler(
-      JSON_Parser parser)
-{
-   (void)parser;
-   return JSON_Parser_Continue;
-}
-
-static JSON_Parser_HandlerResult JSON_CALL NullHandler(
-      JSON_Parser parser)
-{
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
-   (void)pCtx;
-   return JSON_Parser_Continue;
-}
-
-static JSON_Parser_HandlerResult JSON_CALL BooleanHandler(
-      JSON_Parser parser, JSON_Boolean value)
-{
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
+   struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
 
    if (pCtx->state == STATE_FIELDS_OBJECT_START)
-      if (pCtx->cur_field)
-         *((bool*)pCtx->cur_member) = value;
+      if (pCtx->cur_member_bool)
+         *pCtx->cur_member_bool = value;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSON_CALL StringHandler(
-      JSON_Parser parser, char* pValue, size_t length,
-      JSON_StringAttributes attributes)
+static bool netplay_json_string(void* ctx, const char* pValue, size_t length)
 {
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
-   (void)attributes;
+   struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
 
    if (pCtx->state == STATE_FIELDS_OBJECT_START)
    {
       if (pValue && length)
       {
-         if (pCtx->cur_field)
+         if (pCtx->cur_member_inthex)
          {
             /* CRC comes in as a string but it is stored
              * as an unsigned casted to int. */
-            if (string_is_equal(pCtx->cur_field, "game_crc"))
-               *((int*)pCtx->cur_member) = (int)strtoul(pValue, NULL, 16);
-            else
-               strlcpy((char*)pCtx->cur_member, pValue, PATH_MAX_LENGTH);
+            *pCtx->cur_member_inthex = (int)strtoul(pValue, NULL, 16);
+         }
+         if (pCtx->cur_member_string)
+         {
+            strlcpy(pCtx->cur_member_string, pValue, pCtx->cur_member_size);
          }
       }
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSON_CALL NumberHandler(
-      JSON_Parser parser, char* pValue, size_t length, JSON_NumberAttributes attributes)
+static bool netplay_json_number(void* ctx, const char* pValue, size_t length)
 {
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
-   (void)attributes;
+   struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
 
    if (pCtx->state == STATE_FIELDS_OBJECT_START)
    {
       if (pValue && length)
-         if (pCtx->cur_field)
-            *((int*)pCtx->cur_member) = (int)strtol(pValue, NULL, 10);
+         if (pCtx->cur_member_int)
+            *pCtx->cur_member_int = (int)strtol(pValue, NULL, 10);
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSON_CALL SpecialNumberHandler(
-      JSON_Parser parser, JSON_SpecialNumber value)
+static bool netplay_json_start_object(void* ctx)
 {
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
-   (void)pCtx;
-   return JSON_Parser_Continue;
-}
-
-static JSON_Parser_HandlerResult JSON_CALL StartObjectHandler(JSON_Parser parser)
-{
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
+   struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
 
    if (pCtx->state == STATE_FIELDS_START)
    {
@@ -167,28 +125,25 @@ static JSON_Parser_HandlerResult JSON_CALL StartObjectHandler(JSON_Parser parser
    else if (pCtx->state == STATE_ARRAY_START)
       pCtx->state = STATE_OBJECT_START;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSON_CALL EndObjectHandler(JSON_Parser parser)
+static bool netplay_json_end_object(void* ctx)
 {
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
+   struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
 
    if (pCtx->state == STATE_FIELDS_OBJECT_START)
       pCtx->state = STATE_ARRAY_START;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSON_CALL ObjectMemberHandler(JSON_Parser parser,
-      char* pValue, size_t length, JSON_StringAttributes attributes)
+static bool netplay_json_object_member(void* ctx, const char* pValue, size_t length)
 {
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
-   (void)attributes;
+   struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
 
    if (!pValue || !length)
-      return JSON_Parser_Continue;
+      return true;
 
    if (pCtx->state == STATE_OBJECT_START && !string_is_empty(pValue)
          && string_is_equal(pValue, "fields"))
@@ -196,176 +151,111 @@ static JSON_Parser_HandlerResult JSON_CALL ObjectMemberHandler(JSON_Parser parse
 
    if (pCtx->state == STATE_FIELDS_OBJECT_START)
    {
-      if (pCtx->cur_field)
-         free(pCtx->cur_field);
-      pCtx->cur_field = NULL;
+      pCtx->cur_member_bool   = NULL;
+      pCtx->cur_member_int    = NULL;
+      pCtx->cur_member_inthex = NULL;
+      pCtx->cur_member_string = NULL;
 
       if (!string_is_empty(pValue))
       {
          if (string_is_equal(pValue, "username"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->nickname;
+            pCtx->cur_member_string = netplay_rooms_data->cur->nickname;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->nickname);
          }
          else if (string_is_equal(pValue, "game_name"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->gamename;
+            pCtx->cur_member_string = netplay_rooms_data->cur->gamename;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->gamename);
          }
          else if (string_is_equal(pValue, "core_name"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->corename;
+            pCtx->cur_member_string = netplay_rooms_data->cur->corename;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->corename);
          }
          else if (string_is_equal(pValue, "ip"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->address;
+            pCtx->cur_member_string = netplay_rooms_data->cur->address;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->address);
          }
          else if (string_is_equal(pValue, "port"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->port;
+            pCtx->cur_member_int    = &netplay_rooms_data->cur->port;
          }
          else if (string_is_equal(pValue, "game_crc"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->gamecrc;
+            pCtx->cur_member_inthex = &netplay_rooms_data->cur->gamecrc;
          }
          else if (string_is_equal(pValue, "core_version"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->coreversion;
+            pCtx->cur_member_string = netplay_rooms_data->cur->coreversion;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->coreversion);
          }
          else if (string_is_equal(pValue, "has_password"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->has_password;
+            pCtx->cur_member_bool   = &netplay_rooms_data->cur->has_password;
          }
          else if (string_is_equal(pValue, "has_spectate_password"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->has_spectate_password;
+            pCtx->cur_member_bool   = &netplay_rooms_data->cur->has_spectate_password;
          }
          else if (string_is_equal(pValue, "fixed"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->fixed;
+            pCtx->cur_member_bool   = &netplay_rooms_data->cur->fixed;
          }
          else if (string_is_equal(pValue, "mitm_ip"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->mitm_address;
+            pCtx->cur_member_string = netplay_rooms_data->cur->mitm_address;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->mitm_address);
          }
          else if (string_is_equal(pValue, "mitm_port"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->mitm_port;
+            pCtx->cur_member_int    = &netplay_rooms_data->cur->mitm_port;
          }
          else if (string_is_equal(pValue, "host_method"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->host_method;
+            pCtx->cur_member_int    = &netplay_rooms_data->cur->host_method;
          }
          else if (string_is_equal(pValue, "retroarch_version"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->retroarch_version;
+            pCtx->cur_member_string = netplay_rooms_data->cur->retroarch_version;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->retroarch_version);
          }
          else if (string_is_equal(pValue, "country"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->country;
+            pCtx->cur_member_string = netplay_rooms_data->cur->country;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->country);
          }
          else if (string_is_equal(pValue, "frontend"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->frontend;
+            pCtx->cur_member_string = netplay_rooms_data->cur->frontend;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->frontend);
          }
          else if (string_is_equal(pValue, "subsystem_name"))
          {
-            pCtx->cur_field       = strdup(pValue);
-            pCtx->cur_member      = &netplay_rooms_data->cur->subsystem_name;
+            pCtx->cur_member_string = netplay_rooms_data->cur->subsystem_name;
+            pCtx->cur_member_size   = sizeof(netplay_rooms_data->cur->subsystem_name);
          }
       }
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSON_CALL StartArrayHandler(JSON_Parser parser)
+static bool netplay_json_start_array(void* ctx)
 {
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
+   struct netplay_json_context* pCtx = (struct netplay_json_context*)ctx;
 
    if (pCtx->state == STATE_START)
       pCtx->state = STATE_ARRAY_START;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSON_CALL EndArrayHandler(JSON_Parser parser)
+static void netplay_rooms_error(void *context, int line, int col, const char* error)
 {
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
-   (void)pCtx;
-   return JSON_Parser_Continue;
-}
-
-static JSON_Parser_HandlerResult JSON_CALL ArrayItemHandler(JSON_Parser parser)
-{
-   Context* pCtx = (Context*)JSON_Parser_GetUserData(parser);
-   (void)parser;
-   (void)pCtx;
-   return JSON_Parser_Continue;
-}
-
-static void parse_context_setup(Context* pCtx)
-{
-   if (JSON_Parser_GetInputEncoding(pCtx->parser) == JSON_UnknownEncoding)
-      JSON_Parser_SetEncodingDetectedHandler(pCtx->parser,
-            &EncodingDetectedHandler);
-
-   JSON_Parser_SetNullHandler(pCtx->parser,          &NullHandler);
-   JSON_Parser_SetBooleanHandler(pCtx->parser,       &BooleanHandler);
-   JSON_Parser_SetStringHandler(pCtx->parser,        &StringHandler);
-   JSON_Parser_SetNumberHandler(pCtx->parser,        &NumberHandler);
-   JSON_Parser_SetSpecialNumberHandler(pCtx->parser, &SpecialNumberHandler);
-   JSON_Parser_SetStartObjectHandler(pCtx->parser,   &StartObjectHandler);
-   JSON_Parser_SetEndObjectHandler(pCtx->parser,     &EndObjectHandler);
-   JSON_Parser_SetObjectMemberHandler(pCtx->parser,  &ObjectMemberHandler);
-   JSON_Parser_SetStartArrayHandler(pCtx->parser,    &StartArrayHandler);
-   JSON_Parser_SetEndArrayHandler(pCtx->parser,      &EndArrayHandler);
-   JSON_Parser_SetArrayItemHandler(pCtx->parser,     &ArrayItemHandler);
-   JSON_Parser_SetUserData(pCtx->parser, pCtx);
-}
-
-static void parse_context_error(Context* pCtx)
-{
-   if (JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler)
-   {
-      JSON_Error error            = JSON_Parser_GetError(pCtx->parser);
-      JSON_Location errorLocation = {0, 0, 0};
-
-      (void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation);
-
-      RARCH_ERR("invalid JSON at line %d, column %d (input byte %d) - %s.\n",
-            (int)errorLocation.line + 1,
-            (int)errorLocation.column + 1,
-            (int)errorLocation.byte,
-            JSON_ErrorString(error));
-   }
-}
-
-static int json_parse(Context* pCtx, const char *buf)
-{
-   if (!JSON_Parser_Parse(pCtx->parser, buf, strlen(buf), JSON_True))
-   {
-      parse_context_error(pCtx);
-      return 0;
-   }
-
-   return 1;
+   RARCH_ERR("[netplay] Error: Invalid JSON at line %d, column %d - %s.\n",
+         line, col, error);
 }
 
 void netplay_rooms_free(void)
@@ -392,7 +282,7 @@ void netplay_rooms_free(void)
 
 int netplay_rooms_parse(const char *buf)
 {
-   Context ctx;
+   struct netplay_json_context ctx;
 
    memset(&ctx, 0, sizeof(ctx));
 
@@ -404,17 +294,17 @@ int netplay_rooms_parse(const char *buf)
    netplay_rooms_data = (struct netplay_rooms*)
       calloc(1, sizeof(*netplay_rooms_data));
 
-   ctx.parser         = JSON_Parser_Create(NULL);
-
-   if (!ctx.parser)
-   {
-      RARCH_ERR("could not allocate memory for JSON parser.\n");
-      return 1;
-   }
-
-   parse_context_setup(&ctx);
-   json_parse(&ctx, buf);
-   parse_context_free(&ctx);
+   rjson_parse_quick(buf, &ctx, 0,
+         netplay_json_object_member,
+         netplay_json_string,
+         netplay_json_number,
+         netplay_json_start_object,
+         netplay_json_end_object,
+         netplay_json_start_array,
+         NULL /* end_array_handler */,
+         netplay_json_boolean,
+         NULL /* null handler */,
+         netplay_rooms_error);
 
    return 0;
 }
diff --git a/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj
index 1003e827e6..d77b11907e 100644
--- a/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj
+++ b/pkg/apple/RetroArch_iOS11_Metal.xcodeproj/project.pbxproj
@@ -147,7 +147,7 @@
 		92B9EB8024E0518700E6CFB2 /* net_http_parse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = net_http_parse.h; sourceTree = "<group>"; };
 		92B9EB8124E0518700E6CFB2 /* retro_timers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = retro_timers.h; sourceTree = "<group>"; };
 		92B9EB8324E0518700E6CFB2 /* m3u_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m3u_file.h; sourceTree = "<group>"; };
-		92B9EB8424E0518700E6CFB2 /* jsonsax.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jsonsax.h; sourceTree = "<group>"; };
+		92B9EB8424E0518700E6CFB2 /* rjson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rjson.h; sourceTree = "<group>"; };
 		92B9EB8524E0518700E6CFB2 /* rxml.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rxml.h; sourceTree = "<group>"; };
 		92B9EB8624E0518700E6CFB2 /* cdfs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cdfs.h; sourceTree = "<group>"; };
 		92B9EB8724E0518700E6CFB2 /* rpng.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rpng.h; sourceTree = "<group>"; };
@@ -156,7 +156,6 @@
 		92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = logiqx_dat.h; sourceTree = "<group>"; };
 		92B9EB8B24E0518700E6CFB2 /* rbmp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rbmp.h; sourceTree = "<group>"; };
 		92B9EB8C24E0518700E6CFB2 /* rwav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rwav.h; sourceTree = "<group>"; };
-		92B9EB8D24E0518700E6CFB2 /* jsonsax_full.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jsonsax_full.h; sourceTree = "<group>"; };
 		92B9EB8E24E0518700E6CFB2 /* rjpeg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rjpeg.h; sourceTree = "<group>"; };
 		92B9EB9024E0518700E6CFB2 /* rglgen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rglgen.h; sourceTree = "<group>"; };
 		92B9EB9124E0518700E6CFB2 /* glsym_es2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glsym_es2.h; sourceTree = "<group>"; };
@@ -580,7 +579,7 @@
 			isa = PBXGroup;
 			children = (
 				92B9EB8324E0518700E6CFB2 /* m3u_file.h */,
-				92B9EB8424E0518700E6CFB2 /* jsonsax.h */,
+				92B9EB8424E0518700E6CFB2 /* rjson.h */,
 				92B9EB8524E0518700E6CFB2 /* rxml.h */,
 				92B9EB8624E0518700E6CFB2 /* cdfs.h */,
 				92B9EB8724E0518700E6CFB2 /* rpng.h */,
@@ -589,7 +588,6 @@
 				92B9EB8A24E0518700E6CFB2 /* logiqx_dat.h */,
 				92B9EB8B24E0518700E6CFB2 /* rbmp.h */,
 				92B9EB8C24E0518700E6CFB2 /* rwav.h */,
-				92B9EB8D24E0518700E6CFB2 /* jsonsax_full.h */,
 				92B9EB8E24E0518700E6CFB2 /* rjpeg.h */,
 			);
 			path = formats;
diff --git a/playlist.c b/playlist.c
index c9a3add3c0..63a0ccd813 100644
--- a/playlist.c
+++ b/playlist.c
@@ -29,7 +29,7 @@
 #include <streams/interface_stream.h>
 #include <file/file_path.h>
 #include <lists/string_list.h>
-#include <formats/jsonsax_full.h>
+#include <formats/rjson.h>
 #include <array/rbuf.h>
 
 #include "playlist.h"
@@ -80,20 +80,12 @@ struct content_playlist
 typedef struct
 {
    struct playlist_entry *current_entry;
-   char *current_meta_string;
-   char *current_items_string;
-   char **current_entry_val;
-   char **current_meta_val;
-   int *current_entry_int_val;
+   char **current_string_val;
    unsigned *current_entry_uint_val;
-   struct string_list **current_entry_string_list_val;
    enum playlist_label_display_mode *current_meta_label_display_mode_val;
    enum playlist_thumbnail_mode *current_meta_thumbnail_mode_val;
    enum playlist_sort_mode *current_meta_sort_mode_val;
-   intfstream_t *file;
    playlist_t *playlist;
-   JSON_Parser parser;     /* ptr alignment */
-   JSON_Writer writer;     /* ptr alignment */
 
    unsigned array_depth;
    unsigned object_depth;
@@ -1181,39 +1173,11 @@ success:
    return true;
 }
 
-static JSON_Writer_HandlerResult JSONOutputHandler(JSON_Writer writer, const char *pBytes, size_t length)
-{
-   JSONContext *context = (JSONContext*)JSON_Writer_GetUserData(writer);
-
-   (void)writer; /* unused */
-   return intfstream_write(context->file, pBytes, length) == length ? JSON_Writer_Continue : JSON_Writer_Abort;
-}
-
-static void JSONLogError(JSONContext *pCtx)
-{
-   if (pCtx->parser && JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler)
-   {
-      JSON_Error error            = JSON_Parser_GetError(pCtx->parser);
-      JSON_Location errorLocation = { 0, 0, 0 };
-
-      (void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation);
-      RARCH_WARN("Error: Invalid JSON at line %d, column %d (input byte %d) - %s.\n",
-            (int)errorLocation.line + 1,
-            (int)errorLocation.column + 1,
-            (int)errorLocation.byte,
-            JSON_ErrorString(error));
-   }
-   else if (pCtx->writer && JSON_Writer_GetError(pCtx->writer) != JSON_Error_AbortedByHandler)
-   {
-      RARCH_WARN("Error: could not write output - %s.\n", JSON_ErrorString(JSON_Writer_GetError(pCtx->writer)));
-   }
-}
-
 void playlist_write_runtime_file(playlist_t *playlist)
 {
    size_t i, len;
    intfstream_t *file  = NULL;
-   JSONContext context = {0};
+   rjsonwriter_t* writer;
 
    if (!playlist || !playlist->modified)
       return;
@@ -1227,211 +1191,137 @@ void playlist_write_runtime_file(playlist_t *playlist)
       return;
    }
 
-   context.writer = JSON_Writer_Create(NULL);
-   context.file   = file;
-
-   if (!context.writer)
+   writer = rjsonwriter_open_stream(file);
+   if (!writer)
    {
       RARCH_ERR("Failed to create JSON writer\n");
       goto end;
    }
 
-   JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8);
-   JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler);
-   JSON_Writer_SetUserData(context.writer, &context);
-
-   JSON_Writer_WriteStartObject(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "version",
-         STRLEN_CONST("version"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteString(context.writer, "1.0",
-         STRLEN_CONST("1.0"), JSON_UTF8);
-   JSON_Writer_WriteComma(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "items",
-         STRLEN_CONST("items"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteStartArray(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_start_object(writer);
+   rjsonwriter_add_newline(writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "version");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_string(writer, "1.0");
+   rjsonwriter_add_comma(writer);
+   rjsonwriter_add_newline(writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "items");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_start_array(writer);
+   rjsonwriter_add_newline(writer);
 
    for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
    {
-      JSON_Writer_WriteSpace(context.writer, 4);
-      JSON_Writer_WriteStartObject(context.writer);
+      rjsonwriter_add_spaces(writer, 4);
+      rjsonwriter_add_start_object(writer);
 
-      JSON_Writer_WriteNewLine(context.writer);
-      JSON_Writer_WriteSpace(context.writer, 6);
-      JSON_Writer_WriteString(context.writer, "path",
-            STRLEN_CONST("path"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      JSON_Writer_WriteSpace(context.writer, 1);
-      JSON_Writer_WriteString(context.writer,
-            playlist->entries[i].path
-            ? playlist->entries[i].path
-            : "",
-            playlist->entries[i].path
-            ? strlen(playlist->entries[i].path)
-            : 0,
-            JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
+      rjsonwriter_add_newline(writer);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "path");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_string(writer, playlist->entries[i].path);
+      rjsonwriter_add_comma(writer);
 
-      JSON_Writer_WriteNewLine(context.writer);
-      JSON_Writer_WriteSpace(context.writer, 6);
-      JSON_Writer_WriteString(context.writer, "core_path",
-            STRLEN_CONST("core_path"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      JSON_Writer_WriteSpace(context.writer, 1);
-      JSON_Writer_WriteString(context.writer,
-            playlist->entries[i].core_path
-            ? playlist->entries[i].core_path
-            : "",
-            playlist->entries[i].core_path
-            ? strlen(playlist->entries[i].core_path)
-            : 0,
-            JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      JSON_Writer_WriteNewLine(context.writer);
+      rjsonwriter_add_newline(writer);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "core_path");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_string(writer, playlist->entries[i].core_path);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-      {
-         char tmp[32] = {0};
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "runtime_hours");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].runtime_hours);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_hours);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "runtime_minutes");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].runtime_minutes);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "runtime_hours",
-               STRLEN_CONST("runtime_hours"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "runtime_seconds");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].runtime_seconds);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         memset(tmp, 0, sizeof(tmp));
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "last_played_year");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_year);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_minutes);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "last_played_month");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_month);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "runtime_minutes",
-               STRLEN_CONST("runtime_minutes"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "last_played_day");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_day);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         memset(tmp, 0, sizeof(tmp));
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "last_played_hour");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_hour);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_seconds);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "last_played_minute");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_minute);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "runtime_seconds",
-               STRLEN_CONST("runtime_seconds"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
+      rjsonwriter_add_spaces(writer, 6);
+      rjsonwriter_add_string(writer, "last_played_second");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_unsigned(writer, playlist->entries[i].last_played_second);
+      rjsonwriter_add_newline(writer);
 
-         memset(tmp, 0, sizeof(tmp));
-
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_year);
-
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "last_played_year",
-               STRLEN_CONST("last_played_year"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
-
-         memset(tmp, 0, sizeof(tmp));
-
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_month);
-
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "last_played_month",
-               STRLEN_CONST("last_played_month"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
-
-         memset(tmp, 0, sizeof(tmp));
-
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_day);
-
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "last_played_day",
-               STRLEN_CONST("last_played_day"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp,
-               strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
-
-         memset(tmp, 0, sizeof(tmp));
-
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_hour);
-
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "last_played_hour",
-               STRLEN_CONST("last_played_hour"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
-
-         memset(tmp, 0, sizeof(tmp));
-
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_minute);
-
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "last_played_minute",
-               STRLEN_CONST("last_played_minute"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         JSON_Writer_WriteNewLine(context.writer);
-
-         memset(tmp, 0, sizeof(tmp));
-
-         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_second);
-
-         JSON_Writer_WriteSpace(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "last_played_second",
-               STRLEN_CONST("last_played_second"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         JSON_Writer_WriteSpace(context.writer, 1);
-         JSON_Writer_WriteNumber(context.writer, tmp,
-               strlen(tmp), JSON_UTF8);
-         JSON_Writer_WriteNewLine(context.writer);
-      }
-
-      JSON_Writer_WriteSpace(context.writer, 4);
-      JSON_Writer_WriteEndObject(context.writer);
+      rjsonwriter_add_spaces(writer, 4);
+      rjsonwriter_add_end_object(writer);
 
       if (i < len - 1)
-         JSON_Writer_WriteComma(context.writer);
+         rjsonwriter_add_comma(writer);
 
-      JSON_Writer_WriteNewLine(context.writer);
+      rjsonwriter_add_newline(writer);
    }
 
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteEndArray(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
-   JSON_Writer_WriteEndObject(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
-   JSON_Writer_Free(context.writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_end_array(writer);
+   rjsonwriter_add_newline(writer);
+   rjsonwriter_add_end_object(writer);
+   rjsonwriter_add_newline(writer);
+   rjsonwriter_free(writer);
 
    playlist->modified        = false;
    playlist->old_format      = false;
@@ -1443,18 +1333,6 @@ end:
    free(file);
 }
 
-/* No-op versions of JSON whitespace writers,
- * used when generating compressed output */
-static JSON_Status JSON_CALL JSON_Writer_WriteNewLine_NULL(JSON_Writer writer)
-{
-   return JSON_Success;
-}
-
-static JSON_Status JSON_CALL JSON_Writer_WriteSpace_NULL(JSON_Writer writer, size_t numberOfSpaces)
-{
-   return JSON_Success;
-}
-
 void playlist_write_file(playlist_t *playlist)
 {
    size_t i, len;
@@ -1530,283 +1408,169 @@ void playlist_write_file(playlist_t *playlist)
    else
 #endif
    {
-      char uint_str[4];
-      JSONContext context = {0};
-
-      /* Assign JSON whitespace functions
-      * > When compressing playlists, human readability
-      *   is not a factor - can skip all indentation
-      *   and new line characters
-      * > Create these function pointers locally to
-      *   ensure thread safety */
-      JSON_Status (JSON_CALL *json_write_new_line)(JSON_Writer writer) =
-            compressed ?
-                  JSON_Writer_WriteNewLine_NULL :
-                  JSON_Writer_WriteNewLine;
-      JSON_Status (JSON_CALL *json_write_space)(JSON_Writer writer, size_t numberOfSpaces) =
-            compressed ?
-               JSON_Writer_WriteSpace_NULL :
-               JSON_Writer_WriteSpace;
-
-      context.writer = JSON_Writer_Create(NULL);
-      context.file   = file;
-
-      if (!context.writer)
+      rjsonwriter_t* writer = rjsonwriter_open_stream(file);
+      if (!writer)
       {
          RARCH_ERR("Failed to create JSON writer\n");
          goto end;
       }
+      if (compressed)
+      {
+         /*  When compressing playlists, human readability
+          *   is not a factor - can skip all indentation
+          *   and new line characters */
+         rjsonwriter_set_options(writer, RJSONWRITER_OPTION_SKIP_WHITESPACE);
+      }
 
-      JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8);
-      JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler);
-      JSON_Writer_SetUserData(context.writer, &context);
+      rjsonwriter_add_start_object(writer);
+      rjsonwriter_add_newline(writer);
 
-      JSON_Writer_WriteStartObject(context.writer);
-      json_write_new_line(context.writer);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "version");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_string(writer, "1.4");
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "version",
-            STRLEN_CONST("version"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteString(context.writer, "1.4",
-            STRLEN_CONST("1.4"), JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      json_write_new_line(context.writer);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "default_core_path");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_string(writer, playlist->default_core_path);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "default_core_path",
-            STRLEN_CONST("default_core_path"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteString(context.writer,
-            playlist->default_core_path
-            ? playlist->default_core_path
-            : "",
-            playlist->default_core_path
-            ? strlen(playlist->default_core_path)
-            : 0,
-            JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      json_write_new_line(context.writer);
-
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "default_core_name",
-            STRLEN_CONST("default_core_name"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteString(context.writer,
-            playlist->default_core_name
-            ? playlist->default_core_name
-            : "",
-            playlist->default_core_name
-            ? strlen(playlist->default_core_name)
-            : 0,
-            JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      json_write_new_line(context.writer);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "default_core_name");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_string(writer, playlist->default_core_name);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
       if (!string_is_empty(playlist->base_content_directory))
       {
-         json_write_space(context.writer, 2);
-         JSON_Writer_WriteString(context.writer, "base_content_directory",
-            STRLEN_CONST("base_content_directory"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         json_write_space(context.writer, 1);
-         JSON_Writer_WriteString(context.writer,
-            playlist->base_content_directory,
-            strlen(playlist->base_content_directory),
-            JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
-         json_write_new_line(context.writer);
+         rjsonwriter_add_spaces(writer, 2);
+         rjsonwriter_add_string(writer, "base_content_directory");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->base_content_directory);
+         rjsonwriter_add_comma(writer);
+         rjsonwriter_add_newline(writer);
       }
 
-      uint_str[0] = '\0';
-      snprintf(uint_str, sizeof(uint_str), "%u", playlist->label_display_mode);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "label_display_mode");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_int(writer, (int)playlist->label_display_mode);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "label_display_mode",
-            STRLEN_CONST("label_display_mode"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteNumber(context.writer, uint_str,
-            strlen(uint_str), JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      json_write_new_line(context.writer);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "right_thumbnail_mode");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_int(writer, (int)playlist->right_thumbnail_mode);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-      uint_str[0] = '\0';
-      snprintf(uint_str, sizeof(uint_str), "%u", playlist->right_thumbnail_mode);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "left_thumbnail_mode");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_int(writer, (int)playlist->left_thumbnail_mode);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "right_thumbnail_mode",
-            STRLEN_CONST("right_thumbnail_mode"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteNumber(context.writer, uint_str,
-            strlen(uint_str), JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      json_write_new_line(context.writer);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "sort_mode");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_int(writer, (int)playlist->sort_mode);
+      rjsonwriter_add_comma(writer);
+      rjsonwriter_add_newline(writer);
 
-      uint_str[0] = '\0';
-      snprintf(uint_str, sizeof(uint_str), "%u", playlist->left_thumbnail_mode);
-
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "left_thumbnail_mode",
-            STRLEN_CONST("left_thumbnail_mode"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteNumber(context.writer, uint_str,
-            strlen(uint_str), JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      json_write_new_line(context.writer);
-
-      uint_str[0] = '\0';
-      snprintf(uint_str, sizeof(uint_str), "%u", playlist->sort_mode);
-
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "sort_mode",
-            STRLEN_CONST("sort_mode"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteNumber(context.writer, uint_str,
-            strlen(uint_str), JSON_UTF8);
-      JSON_Writer_WriteComma(context.writer);
-      json_write_new_line(context.writer);
-
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteString(context.writer, "items",
-            STRLEN_CONST("items"), JSON_UTF8);
-      JSON_Writer_WriteColon(context.writer);
-      json_write_space(context.writer, 1);
-      JSON_Writer_WriteStartArray(context.writer);
-      json_write_new_line(context.writer);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_string(writer, "items");
+      rjsonwriter_add_colon(writer);
+      rjsonwriter_add_space(writer);
+      rjsonwriter_add_start_array(writer);
+      rjsonwriter_add_newline(writer);
 
       for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
       {
-         json_write_space(context.writer, 4);
-         JSON_Writer_WriteStartObject(context.writer);
+         rjsonwriter_add_spaces(writer, 4);
+         rjsonwriter_add_start_object(writer);
 
-         json_write_new_line(context.writer);
-         json_write_space(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "path",
-               STRLEN_CONST("path"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         json_write_space(context.writer, 1);
-         JSON_Writer_WriteString(context.writer,
-               playlist->entries[i].path
-               ? playlist->entries[i].path
-               : "",
-               playlist->entries[i].path
-               ? strlen(playlist->entries[i].path)
-               : 0,
-               JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
+         rjsonwriter_add_newline(writer);
+         rjsonwriter_add_spaces(writer, 6);
+         rjsonwriter_add_string(writer, "path");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->entries[i].path);
+         rjsonwriter_add_comma(writer);
 
-         json_write_new_line(context.writer);
-         json_write_space(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "label",
-               STRLEN_CONST("label"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         json_write_space(context.writer, 1);
-         JSON_Writer_WriteString(context.writer,
-               playlist->entries[i].label
-               ? playlist->entries[i].label
-               : "",
-               playlist->entries[i].label
-               ? strlen(playlist->entries[i].label)
-               : 0,
-               JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
+         rjsonwriter_add_newline(writer);
+         rjsonwriter_add_spaces(writer, 6);
+         rjsonwriter_add_string(writer, "label");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->entries[i].label);
+         rjsonwriter_add_comma(writer);
 
-         json_write_new_line(context.writer);
-         json_write_space(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "core_path",
-               STRLEN_CONST("core_path"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         json_write_space(context.writer, 1);
-         JSON_Writer_WriteString(context.writer,
-               playlist->entries[i].core_path
-               ? playlist->entries[i].core_path
-               : "",
-               playlist->entries[i].core_path
-               ? strlen(playlist->entries[i].core_path)
-               : 0,
-               JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
+         rjsonwriter_add_newline(writer);
+         rjsonwriter_add_spaces(writer, 6);
+         rjsonwriter_add_string(writer, "core_path");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->entries[i].core_path);
+         rjsonwriter_add_comma(writer);
 
-         json_write_new_line(context.writer);
-         json_write_space(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "core_name",
-               STRLEN_CONST("core_name"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         json_write_space(context.writer, 1);
-         JSON_Writer_WriteString(context.writer,
-               playlist->entries[i].core_name
-               ? playlist->entries[i].core_name
-               : "",
-               playlist->entries[i].core_name
-               ? strlen(playlist->entries[i].core_name)
-               : 0,
-               JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
+         rjsonwriter_add_newline(writer);
+         rjsonwriter_add_spaces(writer, 6);
+         rjsonwriter_add_string(writer, "core_name");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->entries[i].core_name);
+         rjsonwriter_add_comma(writer);
 
-         json_write_new_line(context.writer);
-         json_write_space(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "crc32",
-               STRLEN_CONST("crc32"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         json_write_space(context.writer, 1);
-         JSON_Writer_WriteString(context.writer, playlist->entries[i].crc32 ? playlist->entries[i].crc32 : "",
-               playlist->entries[i].crc32
-               ? strlen(playlist->entries[i].crc32)
-               : 0,
-               JSON_UTF8);
-         JSON_Writer_WriteComma(context.writer);
+         rjsonwriter_add_newline(writer);
+         rjsonwriter_add_spaces(writer, 6);
+         rjsonwriter_add_string(writer, "crc32");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->entries[i].crc32);
+         rjsonwriter_add_comma(writer);
 
-         json_write_new_line(context.writer);
-         json_write_space(context.writer, 6);
-         JSON_Writer_WriteString(context.writer, "db_name",
-               STRLEN_CONST("db_name"), JSON_UTF8);
-         JSON_Writer_WriteColon(context.writer);
-         json_write_space(context.writer, 1);
-         JSON_Writer_WriteString(context.writer, playlist->entries[i].db_name ? playlist->entries[i].db_name : "",
-               playlist->entries[i].db_name
-               ? strlen(playlist->entries[i].db_name)
-               : 0,
-               JSON_UTF8);
+         rjsonwriter_add_newline(writer);
+         rjsonwriter_add_spaces(writer, 6);
+         rjsonwriter_add_string(writer, "db_name");
+         rjsonwriter_add_colon(writer);
+         rjsonwriter_add_space(writer);
+         rjsonwriter_add_string(writer, playlist->entries[i].db_name);
 
          if (!string_is_empty(playlist->entries[i].subsystem_ident))
          {
-            JSON_Writer_WriteComma(context.writer);
-            json_write_new_line(context.writer);
-            json_write_space(context.writer, 6);
-            JSON_Writer_WriteString(context.writer, "subsystem_ident",
-                  STRLEN_CONST("subsystem_ident"), JSON_UTF8);
-            JSON_Writer_WriteColon(context.writer);
-            json_write_space(context.writer, 1);
-            JSON_Writer_WriteString(context.writer, playlist->entries[i].subsystem_ident ? playlist->entries[i].subsystem_ident : "",
-                  playlist->entries[i].subsystem_ident
-                  ? strlen(playlist->entries[i].subsystem_ident)
-                  : 0,
-                  JSON_UTF8);
+            rjsonwriter_add_comma(writer);
+            rjsonwriter_add_newline(writer);
+            rjsonwriter_add_spaces(writer, 6);
+            rjsonwriter_add_string(writer, "subsystem_ident");
+            rjsonwriter_add_colon(writer);
+            rjsonwriter_add_space(writer);
+            rjsonwriter_add_string(writer, playlist->entries[i].subsystem_ident);
          }
 
          if (!string_is_empty(playlist->entries[i].subsystem_name))
          {
-            JSON_Writer_WriteComma(context.writer);
-            json_write_new_line(context.writer);
-            json_write_space(context.writer, 6);
-            JSON_Writer_WriteString(context.writer, "subsystem_name",
-                  STRLEN_CONST("subsystem_name"), JSON_UTF8);
-            JSON_Writer_WriteColon(context.writer);
-            json_write_space(context.writer, 1);
-            JSON_Writer_WriteString(context.writer,
-                  playlist->entries[i].subsystem_name
-                  ? playlist->entries[i].subsystem_name
-                  : "",
-                  playlist->entries[i].subsystem_name
-                  ? strlen(playlist->entries[i].subsystem_name)
-                  : 0, JSON_UTF8);
+            rjsonwriter_add_comma(writer);
+            rjsonwriter_add_newline(writer);
+            rjsonwriter_add_spaces(writer, 6);
+            rjsonwriter_add_string(writer, "subsystem_name");
+            rjsonwriter_add_colon(writer);
+            rjsonwriter_add_space(writer);
+            rjsonwriter_add_string(writer, playlist->entries[i].subsystem_name);
          }
 
          if (  playlist->entries[i].subsystem_roms &&
@@ -1814,58 +1578,57 @@ void playlist_write_file(playlist_t *playlist)
          {
             unsigned j;
 
-            JSON_Writer_WriteComma(context.writer);
-            json_write_new_line(context.writer);
-            json_write_space(context.writer, 6);
-            JSON_Writer_WriteString(context.writer, "subsystem_roms",
-                  STRLEN_CONST("subsystem_roms"), JSON_UTF8);
-            JSON_Writer_WriteColon(context.writer);
-            json_write_space(context.writer, 1);
-            JSON_Writer_WriteStartArray(context.writer);
-            json_write_new_line(context.writer);
+            rjsonwriter_add_comma(writer);
+            rjsonwriter_add_newline(writer);
+            rjsonwriter_add_spaces(writer, 6);
+            rjsonwriter_add_string(writer, "subsystem_roms");
+            rjsonwriter_add_colon(writer);
+            rjsonwriter_add_space(writer);
+            rjsonwriter_add_start_array(writer);
+            rjsonwriter_add_newline(writer);
 
             for (j = 0; j < playlist->entries[i].subsystem_roms->size; j++)
             {
                const struct string_list *roms = playlist->entries[i].subsystem_roms;
-               json_write_space(context.writer, 8);
-               JSON_Writer_WriteString(context.writer,
+               rjsonwriter_add_spaces(writer, 8);
+               rjsonwriter_add_string(writer,
                      !string_is_empty(roms->elems[j].data)
                      ? roms->elems[j].data
-                     : "",
-                     !string_is_empty(roms->elems[j].data)
-                     ? strlen(roms->elems[j].data)
-                     : 0,
-                     JSON_UTF8);
+                     : "");
 
                if (j < playlist->entries[i].subsystem_roms->size - 1)
                {
-                  JSON_Writer_WriteComma(context.writer);
-                  json_write_new_line(context.writer);
+                  rjsonwriter_add_comma(writer);
+                  rjsonwriter_add_newline(writer);
                }
             }
 
-            json_write_new_line(context.writer);
-            json_write_space(context.writer, 6);
-            JSON_Writer_WriteEndArray(context.writer);
+            rjsonwriter_add_newline(writer);
+            rjsonwriter_add_spaces(writer, 6);
+            rjsonwriter_add_end_array(writer);
          }
 
-         json_write_new_line(context.writer);
+         rjsonwriter_add_newline(writer);
 
-         json_write_space(context.writer, 4);
-         JSON_Writer_WriteEndObject(context.writer);
+         rjsonwriter_add_spaces(writer, 4);
+         rjsonwriter_add_end_object(writer);
 
          if (i < len - 1)
-            JSON_Writer_WriteComma(context.writer);
+            rjsonwriter_add_comma(writer);
 
-         json_write_new_line(context.writer);
+         rjsonwriter_add_newline(writer);
       }
 
-      json_write_space(context.writer, 2);
-      JSON_Writer_WriteEndArray(context.writer);
-      json_write_new_line(context.writer);
-      JSON_Writer_WriteEndObject(context.writer);
-      json_write_new_line(context.writer);
-      JSON_Writer_Free(context.writer);
+      rjsonwriter_add_spaces(writer, 2);
+      rjsonwriter_add_end_array(writer);
+      rjsonwriter_add_newline(writer);
+      rjsonwriter_add_end_object(writer);
+      rjsonwriter_add_newline(writer);
+
+      if (!rjsonwriter_free(writer))
+      {
+         RARCH_ERR("Failed to write to playlist file: %s\n", playlist->config.path);
+      }
 
       playlist->old_format = false;
    }
@@ -1970,62 +1733,38 @@ size_t playlist_capacity(playlist_t *playlist)
    return playlist->config.capacity;
 }
 
-static JSON_Parser_HandlerResult JSONStartArrayHandler(JSON_Parser parser)
+static bool JSONStartArrayHandler(void *context)
 {
-   JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser);
+   JSONContext *pCtx = (JSONContext *)context;
 
    pCtx->array_depth++;
 
-   if (pCtx->object_depth == 1)
-   {
-      if (string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 1)
-         pCtx->in_items = true;
-   }
-   else if (pCtx->object_depth == 2)
-   {
-      if (pCtx->array_depth == 2)
-         if (string_is_equal(pCtx->current_items_string, "subsystem_roms"))
-            pCtx->in_subsystem_roms = true;
-   }
-
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSONEndArrayHandler(JSON_Parser parser)
+static bool JSONEndArrayHandler(void *context)
 {
-   JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser);
+   JSONContext *pCtx = (JSONContext *)context;
 
    retro_assert(pCtx->array_depth > 0);
 
    pCtx->array_depth--;
 
-   if (pCtx->object_depth == 1)
+   if (pCtx->in_items && pCtx->array_depth == 0 && pCtx->object_depth <= 1)
    {
-      if (pCtx->in_items && string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 0)
-      {
-         free(pCtx->current_meta_string);
-         pCtx->current_meta_string = NULL;
-         pCtx->in_items = false;
-
-         if (pCtx->current_items_string)
-         {
-            free(pCtx->current_items_string);
-            pCtx->current_items_string = NULL;
-         }
-      }
+      pCtx->in_items = false;
    }
-   else if (pCtx->object_depth == 2)
+   else if (pCtx->in_subsystem_roms && pCtx->array_depth <= 1 && pCtx->object_depth <= 2)
    {
-      if (pCtx->in_subsystem_roms && string_is_equal(pCtx->current_items_string, "subsystem_roms") && pCtx->array_depth == 1)
-         pCtx->in_subsystem_roms = false;
+      pCtx->in_subsystem_roms = false;
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser)
+static bool JSONStartObjectHandler(void *context)
 {
-   JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser);
+   JSONContext *pCtx = (JSONContext *)context;
 
    pCtx->object_depth++;
 
@@ -2041,7 +1780,7 @@ static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser)
             if (!RBUF_TRYFIT(pCtx->playlist->entries, len + 1))
             {
                pCtx->out_of_memory     = true;
-               return JSON_Parser_Abort;
+               return false;
             }
             pCtx->current_entry = &pCtx->playlist->entries[len];
             memset(pCtx->current_entry, 0, sizeof(*pCtx->current_entry));
@@ -2063,12 +1802,12 @@ static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser)
       }
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser)
+static bool JSONEndObjectHandler(void *context)
 {
-   JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser);
+   JSONContext *pCtx = (JSONContext *)context;
 
    if (pCtx->in_items && pCtx->object_depth == 2)
    {
@@ -2081,35 +1820,34 @@ static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser)
 
    pCtx->object_depth--;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSONStringHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes)
+static bool JSONStringHandler(void *context, const char *pValue, size_t length)
 {
-   JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   JSONContext *pCtx = (JSONContext *)context;
 
    if (pCtx->in_items && pCtx->in_subsystem_roms && pCtx->object_depth == 2 && pCtx->array_depth == 2)
    {
-      if (pCtx->current_entry_string_list_val && length && !string_is_empty(pValue))
+      if (length && !string_is_empty(pValue))
       {
          union string_list_elem_attr attr = {0};
 
-         if (!*pCtx->current_entry_string_list_val)
-            *pCtx->current_entry_string_list_val = string_list_new();
+         if (!pCtx->current_entry->subsystem_roms)
+            pCtx->current_entry->subsystem_roms = string_list_new();
 
-         string_list_append(*pCtx->current_entry_string_list_val, pValue, attr);
+         string_list_append(pCtx->current_entry->subsystem_roms, pValue, attr);
       }
    }
    else if (pCtx->in_items && pCtx->object_depth == 2)
    {
       if (pCtx->array_depth == 1)
       {
-         if (pCtx->current_entry_val && length && !string_is_empty(pValue))
+         if (pCtx->current_string_val && length && !string_is_empty(pValue))
          {
-            if (*pCtx->current_entry_val)
-               free(*pCtx->current_entry_val);
-            *pCtx->current_entry_val = strdup(pValue);
+            if (*pCtx->current_string_val)
+                free(*pCtx->current_string_val);
+             *pCtx->current_string_val = strdup(pValue);
          }
       }
    }
@@ -2117,42 +1855,30 @@ static JSON_Parser_HandlerResult JSONStringHandler(JSON_Parser parser, char *pVa
    {
       if (pCtx->array_depth == 0)
       {
-         if (pCtx->current_meta_val && length && !string_is_empty(pValue))
+         if (pCtx->current_string_val && length && !string_is_empty(pValue))
          {
             /* handle any top-level playlist metadata here */
-#if 0
-            RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue);
-#endif
-
-            free(pCtx->current_meta_string);
-            pCtx->current_meta_string = NULL;
-
-            if (*pCtx->current_meta_val)
-               free(*pCtx->current_meta_val);
-
-            *pCtx->current_meta_val = strdup(pValue);
+            if (*pCtx->current_string_val)
+                free(*pCtx->current_string_val);
+            *pCtx->current_string_val = strdup(pValue);
          }
       }
    }
 
-   pCtx->current_entry_val = NULL;
-   pCtx->current_meta_val  = NULL;
+   pCtx->current_string_val = NULL;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes)
+static bool JSONNumberHandler(void *context, const char *pValue, size_t length)
 {
-   JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   JSONContext *pCtx = (JSONContext *)context;
 
    if (pCtx->in_items && pCtx->object_depth == 2)
    {
-      if (pCtx->array_depth == 1)
+      if (pCtx->array_depth == 1 && length && !string_is_empty(pValue))
       {
-         if (pCtx->current_entry_int_val && length && !string_is_empty(pValue))
-            *pCtx->current_entry_int_val = (int)strtoul(pValue, NULL, 10);
-         else if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue))
+         if (pCtx->current_entry_uint_val)
             *pCtx->current_entry_uint_val = (unsigned)strtoul(pValue, NULL, 10);
       }
    }
@@ -2160,16 +1886,9 @@ static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pVa
    {
       if (pCtx->array_depth == 0)
       {
-         if (pCtx->current_meta_string && length && !string_is_empty(pValue))
+         if (length && !string_is_empty(pValue))
          {
             /* handle any top-level playlist metadata here */
-#if 0
-            RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue);
-#endif
-
-            free(pCtx->current_meta_string);
-            pCtx->current_meta_string = NULL;
-
             if (pCtx->current_meta_label_display_mode_val)
                *pCtx->current_meta_label_display_mode_val = (enum playlist_label_display_mode)strtoul(pValue, NULL, 10);
             else if (pCtx->current_meta_thumbnail_mode_val)
@@ -2180,134 +1899,128 @@ static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pVa
       }
    }
 
-   pCtx->current_entry_int_val               = NULL;
    pCtx->current_entry_uint_val              = NULL;
    pCtx->current_meta_label_display_mode_val = NULL;
    pCtx->current_meta_thumbnail_mode_val     = NULL;
    pCtx->current_meta_sort_mode_val          = NULL;
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult JSONObjectMemberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes)
+static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t length)
 {
-   JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   JSONContext *pCtx = (JSONContext *)context;
 
    if (pCtx->in_items && pCtx->object_depth == 2)
    {
       if (pCtx->array_depth == 1)
       {
-         if (pCtx->current_entry_val)
+         if (pCtx->current_string_val)
          {
             /* something went wrong */
-            RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__);
-            return JSON_Parser_Abort;
+            return false;
          }
 
-         if (length)
+         if (length && !pCtx->capacity_exceeded)
          {
-            if (!string_is_empty(pValue))
+            pCtx->current_string_val     = NULL;
+            pCtx->current_entry_uint_val = NULL;
+            pCtx->in_subsystem_roms      = false;
+            switch (pValue[0])
             {
-               if (!string_is_empty(pCtx->current_items_string))
-                  free(pCtx->current_items_string);
-               pCtx->current_items_string = strdup(pValue);
-            }
-
-            if (!pCtx->capacity_exceeded)
-            {
-               if (string_is_equal(pValue, "path"))
-                  pCtx->current_entry_val = &pCtx->current_entry->path;
-               else if (string_is_equal(pValue, "label"))
-                  pCtx->current_entry_val = &pCtx->current_entry->label;
-               else if (string_is_equal(pValue, "core_path"))
-                  pCtx->current_entry_val = &pCtx->current_entry->core_path;
-               else if (string_is_equal(pValue, "core_name"))
-                  pCtx->current_entry_val = &pCtx->current_entry->core_name;
-               else if (string_is_equal(pValue, "crc32"))
-                  pCtx->current_entry_val = &pCtx->current_entry->crc32;
-               else if (string_is_equal(pValue, "db_name"))
-                  pCtx->current_entry_val = &pCtx->current_entry->db_name;
-               else if (string_starts_with_size(pValue, "subsystem_", STRLEN_CONST("subsystem_")))
-               {
-                  if (string_is_equal(pValue, "subsystem_ident"))
-                     pCtx->current_entry_val = &pCtx->current_entry->subsystem_ident;
-                  else if (string_is_equal(pValue, "subsystem_name"))
-                     pCtx->current_entry_val = &pCtx->current_entry->subsystem_name;
-                  else if (string_is_equal(pValue, "subsystem_roms"))
-                     pCtx->current_entry_string_list_val = &pCtx->current_entry->subsystem_roms;
-               }
-               else if (string_starts_with_size(pValue, "runtime_", STRLEN_CONST("runtime_")))
-               {
-                  if (string_is_equal(pValue, "runtime_hours"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_hours;
-                  else if (string_is_equal(pValue, "runtime_minutes"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_minutes;
-                  else if (string_is_equal(pValue, "runtime_seconds"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_seconds;
-               }
-               else if (string_starts_with_size(pValue, "last_played_", STRLEN_CONST("last_played_")))
-               {
-                  if (string_is_equal(pValue, "last_played_year"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year;
-                  else if (string_is_equal(pValue, "last_played_month"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month;
-                  else if (string_is_equal(pValue, "last_played_day"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_day;
-                  else if (string_is_equal(pValue, "last_played_hour"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_hour;
-                  else if (string_is_equal(pValue, "last_played_minute"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_minute;
-                  else if (string_is_equal(pValue, "last_played_second"))
-                     pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second;
-               }
-            }
-            else
-            {
-               pCtx->current_entry_val             = NULL;
-               pCtx->current_entry_uint_val        = NULL;
-               pCtx->current_entry_string_list_val = NULL;
+               case 'c':
+                     if (string_is_equal(pValue, "core_name"))
+                        pCtx->current_string_val = &pCtx->current_entry->core_name;
+                     else if (string_is_equal(pValue, "core_path"))
+                        pCtx->current_string_val = &pCtx->current_entry->core_path;
+                     else if (string_is_equal(pValue, "crc32"))
+                        pCtx->current_string_val = &pCtx->current_entry->crc32;
+                     break;
+               case 'd':
+                     if (string_is_equal(pValue, "db_name"))
+                        pCtx->current_string_val = &pCtx->current_entry->db_name;
+                     break;
+               case 'l':
+                     if (string_is_equal(pValue, "label"))
+                        pCtx->current_string_val = &pCtx->current_entry->label;
+                     else if (string_is_equal(pValue, "last_played_day"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_day;
+                     else if (string_is_equal(pValue, "last_played_hour"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_hour;
+                     else if (string_is_equal(pValue, "last_played_minute"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_minute;
+                     else if (string_is_equal(pValue, "last_played_month"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month;
+                     else if (string_is_equal(pValue, "last_played_second"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second;
+                     else if (string_is_equal(pValue, "last_played_year"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year;
+                     break;
+               case 'p':
+                     if (string_is_equal(pValue, "path"))
+                        pCtx->current_string_val = &pCtx->current_entry->path;
+                     break;
+               case 'r':
+                     if (string_is_equal(pValue, "runtime_hours"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_hours;
+                     else if (string_is_equal(pValue, "runtime_minutes"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_minutes;
+                     else if (string_is_equal(pValue, "runtime_seconds"))
+                        pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_seconds;
+                     break;
+               case 's':
+                     if (string_is_equal(pValue, "subsystem_ident"))
+                        pCtx->current_string_val = &pCtx->current_entry->subsystem_ident;
+                     else if (string_is_equal(pValue, "subsystem_name"))
+                        pCtx->current_string_val = &pCtx->current_entry->subsystem_name;
+                     else if (string_is_equal(pValue, "subsystem_roms"))
+                        pCtx->in_subsystem_roms = true;
+                     break;
             }
          }
       }
    }
-   else if (pCtx->object_depth == 1)
+   else if (pCtx->object_depth == 1 && pCtx->array_depth == 0 && length)
    {
-      if (pCtx->array_depth == 0)
+      pCtx->current_string_val                  = NULL;
+      pCtx->current_meta_label_display_mode_val = NULL;
+      pCtx->current_meta_thumbnail_mode_val     = NULL;
+      pCtx->current_meta_sort_mode_val          = NULL;
+      pCtx->in_items                            = false;
+      switch (pValue[0])
       {
-         if (pCtx->current_meta_val)
-         {
-            /* something went wrong */
-            RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__);
-            return JSON_Parser_Abort;
-         }
-
-         if (length)
-         {
-            if (pCtx->current_meta_string)
-               free(pCtx->current_meta_string);
-            pCtx->current_meta_string = strdup(pValue);
-
+         case 'b':
+            if (string_is_equal(pValue, "base_content_directory"))
+               pCtx->current_string_val = &pCtx->playlist->base_content_directory;
+            break;
+         case 'd':
             if (string_is_equal(pValue, "default_core_path"))
-               pCtx->current_meta_val = &pCtx->playlist->default_core_path;
+               pCtx->current_string_val = &pCtx->playlist->default_core_path;
             else if (string_is_equal(pValue, "default_core_name"))
-               pCtx->current_meta_val = &pCtx->playlist->default_core_name;
-            else if (string_is_equal(pValue, "label_display_mode"))
+               pCtx->current_string_val = &pCtx->playlist->default_core_name;
+            break;
+         case 'i':
+            if (string_is_equal(pValue, "items"))
+               pCtx->in_items = true;
+            break;
+         case 'l':
+            if (string_is_equal(pValue, "label_display_mode"))
                pCtx->current_meta_label_display_mode_val = &pCtx->playlist->label_display_mode;
-            else if (string_is_equal(pValue, "right_thumbnail_mode"))
-               pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode;
             else if (string_is_equal(pValue, "left_thumbnail_mode"))
                pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode;
-            else if (string_is_equal(pValue, "sort_mode"))
+            break;
+         case 'r':
+            if (string_is_equal(pValue, "right_thumbnail_mode"))
+               pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode;
+            break;
+         case 's':
+            if (string_is_equal(pValue, "sort_mode"))
                pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode;
-            else if (string_is_equal(pValue, "base_content_directory"))
-               pCtx->current_meta_val = &pCtx->playlist->base_content_directory;
-
-         }
+            break;
       }
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
 static void get_old_format_metadata_value(
@@ -2389,92 +2102,51 @@ static bool playlist_read_file(playlist_t *playlist)
 
    if (!playlist->old_format)
    {
+      rjson_t* parser;
       JSONContext context = {0};
-      context.parser      = JSON_Parser_Create(NULL);
-      context.file        = file;
       context.playlist    = playlist;
 
-      if (!context.parser)
+      parser = rjson_open_stream(file);
+      if (!parser)
       {
          RARCH_ERR("Failed to create JSON parser\n");
          goto end;
       }
 
-#if 0
-      JSON_Parser_SetTrackObjectMembers(context.parser, JSON_True);
-#endif
-      JSON_Parser_SetAllowBOM(context.parser, JSON_True);
-      JSON_Parser_SetAllowComments(context.parser, JSON_True);
-      JSON_Parser_SetAllowSpecialNumbers(context.parser, JSON_True);
-      JSON_Parser_SetAllowHexNumbers(context.parser, JSON_True);
-      JSON_Parser_SetAllowUnescapedControlCharacters(context.parser, JSON_True);
-      JSON_Parser_SetReplaceInvalidEncodingSequences(context.parser, JSON_True);
+      rjson_set_options(parser,
+              RJSON_OPTION_ALLOW_UTF8BOM
+            | RJSON_OPTION_ALLOW_COMMENTS
+            | RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS
+            | RJSON_OPTION_REPLACE_INVALID_ENCODING);
 
-#if 0
-      JSON_Parser_SetNullHandler(context.parser,          &JSONNullHandler);
-      JSON_Parser_SetBooleanHandler(context.parser,       &JSONBooleanHandler);
-      JSON_Parser_SetSpecialNumberHandler(context.parser, &JSONSpecialNumberHandler);
-      JSON_Parser_SetArrayItemHandler(context.parser,     &JSONArrayItemHandler);
-#endif
-
-      JSON_Parser_SetNumberHandler(context.parser,        &JSONNumberHandler);
-      JSON_Parser_SetStringHandler(context.parser,        &JSONStringHandler);
-      JSON_Parser_SetStartObjectHandler(context.parser,   &JSONStartObjectHandler);
-      JSON_Parser_SetEndObjectHandler(context.parser,     &JSONEndObjectHandler);
-      JSON_Parser_SetObjectMemberHandler(context.parser,  &JSONObjectMemberHandler);
-      JSON_Parser_SetStartArrayHandler(context.parser,    &JSONStartArrayHandler);
-      JSON_Parser_SetEndArrayHandler(context.parser,      &JSONEndArrayHandler);
-      JSON_Parser_SetUserData(context.parser, &context);
-
-      while (!intfstream_eof(file))
+      if (rjson_parse(parser, &context,
+            JSONObjectMemberHandler,
+            JSONStringHandler,
+            JSONNumberHandler,
+            JSONStartObjectHandler,
+            JSONEndObjectHandler,
+            JSONStartArrayHandler,
+            JSONEndArrayHandler,
+            NULL, NULL) /* unused boolean/null handlers */
+            != RJSON_DONE)
       {
-         char chunk[4096] = {0};
-         int64_t length = intfstream_read(file, chunk, sizeof(chunk));
-
-         if (!length && !intfstream_eof(file))
+         if (context.out_of_memory)
          {
-            RARCH_WARN("Could not read JSON input.\n");
-            goto json_cleanup;
+            RARCH_WARN("Ran out of memory while parsing JSON playlist\n");
+            res = false;
          }
-
-         if (!JSON_Parser_Parse(context.parser, chunk,
-                  (size_t)length, JSON_False))
+         else
          {
-            if (context.out_of_memory)
-            {
-               RARCH_WARN("Ran out of memory while parsing JSON playlist\n");
-               res = false;
-               goto json_cleanup;
-            }
-            /* Note: Chunk may not be null-terminated.
-             * It is therefore dangerous to print its contents.
-             * Setting a size limit here mitigates the issue, but
-             * in general this is not good practice...
-             * Addendum: RARCH_WARN() actually limits the printed
-             * buffer size anyway, so this warning message is most
-             * likely worthless... */
-            RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n", 4096, chunk);
-            JSONLogError(&context);
-            goto json_cleanup;
+            RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n",
+                  rjson_get_source_context_len(parser),
+                  rjson_get_source_context_buf(parser));
+            RARCH_WARN("Error: Invalid JSON at line %d, column %d - %s.\n",
+                  (int)rjson_get_source_line(parser),
+                  (int)rjson_get_source_column(parser),
+                  (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
          }
       }
-
-      if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True))
-      {
-         RARCH_WARN("Error parsing JSON.\n");
-         JSONLogError(&context);
-         goto json_cleanup;
-      }
-
-json_cleanup:
-
-      JSON_Parser_Free(context.parser);
-
-      if (context.current_meta_string)
-         free(context.current_meta_string);
-
-      if (context.current_items_string)
-         free(context.current_items_string);
+      rjson_free(parser);
    }
    else
    {
diff --git a/runtime_file.c b/runtime_file.c
index 7fc8e0f99c..491e011db2 100644
--- a/runtime_file.c
+++ b/runtime_file.c
@@ -29,7 +29,7 @@
 #include <file/file_path.h>
 #include <retro_miscellaneous.h>
 #include <streams/file_stream.h>
-#include <formats/jsonsax_full.h>
+#include <formats/rjson.h>
 #include <string/stdstring.h>
 #include <encodings/utf.h>
 #include <time/rtime.h>
@@ -53,27 +53,19 @@
 
 typedef struct
 {
-   JSON_Parser parser;
-   JSON_Writer writer;
-   RFILE *file;
    char **current_entry_val;
    char *runtime_string;
    char *last_played_string;
 } RtlJSONContext;
 
-static JSON_Parser_HandlerResult RtlJSONObjectMemberHandler(
-      JSON_Parser parser,
-      char *s, size_t len,
-      JSON_StringAttributes attributes)
+static bool RtlJSONObjectMemberHandler(void *ctx, const char *s, size_t len)
 {
-   RtlJSONContext *p_ctx = (RtlJSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
 
    if (p_ctx->current_entry_val)
    {
       /* something went wrong */
-      RARCH_ERR("JSON parsing failed at line %d.\n", __LINE__);
-      return JSON_Parser_Abort;
+      return false;
    }
 
    if (len)
@@ -85,15 +77,12 @@ static JSON_Parser_HandlerResult RtlJSONObjectMemberHandler(
       /* ignore unknown members */
    }
 
-   return JSON_Parser_Continue;
+   return true;
 }
 
-static JSON_Parser_HandlerResult RtlJSONStringHandler(
-      JSON_Parser parser,
-      char *s, size_t len, JSON_StringAttributes attributes)
+static bool RtlJSONStringHandler(void *ctx, const char *s, size_t len)
 {
-   RtlJSONContext *p_ctx = (RtlJSONContext*)JSON_Parser_GetUserData(parser);
-   (void)attributes; /* unused */
+   RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
 
    if (p_ctx->current_entry_val && len && !string_is_empty(s))
    {
@@ -106,42 +95,7 @@ static JSON_Parser_HandlerResult RtlJSONStringHandler(
 
    p_ctx->current_entry_val = NULL;
 
-   return JSON_Parser_Continue;
-}
-
-static JSON_Writer_HandlerResult RtlJSONOutputHandler(
-      JSON_Writer writer, const char *pBytes, size_t length)
-{
-   RtlJSONContext *context = (RtlJSONContext*)JSON_Writer_GetUserData(writer);
-   (void)writer; /* unused */
-
-   return filestream_write(context->file, pBytes, length) == length 
-      ? JSON_Writer_Continue 
-      : JSON_Writer_Abort;
-}
-
-static void RtlJSONLogError(RtlJSONContext *p_ctx)
-{
-   if (p_ctx->parser && JSON_Parser_GetError(p_ctx->parser) 
-         != JSON_Error_AbortedByHandler)
-   {
-      JSON_Error error            = JSON_Parser_GetError(p_ctx->parser);
-      JSON_Location errorLocation = { 0, 0, 0 };
-
-      (void)JSON_Parser_GetErrorLocation(p_ctx->parser, &errorLocation);
-      RARCH_ERR(
-            "Error: Invalid JSON at line %d, column %d (input byte %d) - %s.\n",
-            (int)errorLocation.line + 1,
-            (int)errorLocation.column + 1,
-            (int)errorLocation.byte,
-            JSON_ErrorString(error));
-   }
-   else if (p_ctx->writer && JSON_Writer_GetError(p_ctx->writer) 
-         != JSON_Error_AbortedByHandler)
-   {
-      RARCH_ERR("Error: could not write output - %s.\n",
-            JSON_ErrorString(JSON_Writer_GetError(p_ctx->writer)));
-   }
+   return true;
 }
 
 /* Initialisation */
@@ -162,6 +116,8 @@ static void runtime_log_read_file(runtime_log_t *runtime_log)
    unsigned last_played_second = 0;
 
    RtlJSONContext context      = {0};
+   rjson_t* parser;
+
    /* Attempt to open log file */
    RFILE *file                 = filestream_open(runtime_log->path,
          RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
@@ -173,61 +129,41 @@ static void runtime_log_read_file(runtime_log_t *runtime_log)
    }
 
    /* Initialise JSON parser */
-   context.runtime_string     = NULL;
-   context.last_played_string = NULL;
-   context.parser             = JSON_Parser_Create(NULL);
-   context.file               = file;
-
-   if (!context.parser)
+   parser = rjson_open_rfile(file);
+   if (!parser)
    {
       RARCH_ERR("Failed to create JSON parser.\n");
       goto end;
    }
 
    /* Configure parser */
-   JSON_Parser_SetAllowBOM(context.parser, JSON_True);
-   JSON_Parser_SetStringHandler(context.parser, &RtlJSONStringHandler);
-   JSON_Parser_SetObjectMemberHandler(context.parser,
-         &RtlJSONObjectMemberHandler);
-   JSON_Parser_SetUserData(context.parser, &context);
+   rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM);
 
    /* Read file */
-   while (!filestream_eof(file))
+   if (rjson_parse(parser, &context,
+         RtlJSONObjectMemberHandler,
+         RtlJSONStringHandler,
+         NULL,                   /* unused number handler */
+         NULL, NULL, NULL, NULL, /* unused object/array handlers */
+         NULL, NULL)             /* unused boolean/null handlers */
+         != RJSON_DONE)
    {
-      /* Runtime log files are tiny - use small chunk size */
-      char chunk[128] = {0};
-      int64_t length  = filestream_read(file, chunk, sizeof(chunk));
-
-      /* Error checking... */
-      if (!length && !filestream_eof(file))
+      if (rjson_get_source_context_len(parser))
       {
-         RARCH_ERR("Failed to read runtime log file: %s\n", runtime_log->path);
-         JSON_Parser_Free(context.parser);
-         goto end;
+         RARCH_ERR("Error parsing chunk of runtime log file: %s\n---snip---\n%.*s\n---snip---\n",
+               runtime_log->path,
+               rjson_get_source_context_len(parser),
+               rjson_get_source_context_buf(parser));
       }
-
-      /* Parse chunk */
-      if (!JSON_Parser_Parse(context.parser, chunk,
-               (size_t)length, JSON_False))
-      {
-         RARCH_ERR("Error parsing chunk of runtime log file: %s\n---snip---\n%s\n---snip---\n", runtime_log->path, chunk);
-         RtlJSONLogError(&context);
-         JSON_Parser_Free(context.parser);
-         goto end;
-      }
-   }
-
-   /* Finalise parsing */
-   if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True))
-   {
       RARCH_WARN("Error parsing runtime log file: %s\n", runtime_log->path);
-      RtlJSONLogError(&context);
-      JSON_Parser_Free(context.parser);
-      goto end;
+      RARCH_ERR("Error: Invalid JSON at line %d, column %d - %s.\n",
+            (int)rjson_get_source_line(parser),
+            (int)rjson_get_source_column(parser),
+            (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
    }
 
    /* Free parser */
-   JSON_Parser_Free(context.parser);
+   rjson_free(parser);
 
    /* Process string values read from JSON file */
 
@@ -1217,8 +1153,8 @@ void runtime_log_save(runtime_log_t *runtime_log)
    int n;
    char value_string[64]; /* 64 characters should be
                              enough for a very long runtime... :) */
-   RtlJSONContext context = {0};
    RFILE *file            = NULL;
+   rjsonwriter_t* writer;
 
    if (!runtime_log)
       return;
@@ -1236,34 +1172,25 @@ void runtime_log_save(runtime_log_t *runtime_log)
    }
 
    /* Initialise JSON writer */
-   context.writer = JSON_Writer_Create(NULL);
-   context.file   = file;
-
-   if (!context.writer)
+   writer = rjsonwriter_open_rfile(file);
+   if (!writer)
    {
       RARCH_ERR("Failed to create JSON writer.\n");
       goto end;
    }
 
-   /* Configure JSON writer */
-   JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8);
-   JSON_Writer_SetOutputHandler(context.writer, &RtlJSONOutputHandler);
-   JSON_Writer_SetUserData(context.writer, &context);
-
    /* Write output file */
-   JSON_Writer_WriteStartObject(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_start_object(writer);
+   rjsonwriter_add_newline(writer);
 
    /* > Version entry */
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "version",
-         STRLEN_CONST("version"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteString(context.writer, "1.0",
-         STRLEN_CONST("1.0"), JSON_UTF8);
-   JSON_Writer_WriteComma(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "version");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_string(writer, "1.0");
+   rjsonwriter_add_comma(writer);
+   rjsonwriter_add_newline(writer);
 
    /* > Runtime entry */
    value_string[0] = '\0';
@@ -1274,15 +1201,13 @@ void runtime_log_save(runtime_log_t *runtime_log)
    if ((n < 0) || (n >= 64))
       n = 0; /* Silence GCC warnings... */
 
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "runtime",
-         STRLEN_CONST("runtime"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteString(context.writer, value_string,
-         strlen(value_string), JSON_UTF8);
-   JSON_Writer_WriteComma(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "runtime");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_string(writer, value_string);
+   rjsonwriter_add_comma(writer);
+   rjsonwriter_add_newline(writer);
 
    /* > Last played entry */
    value_string[0] = '\0';
@@ -1295,21 +1220,22 @@ void runtime_log_save(runtime_log_t *runtime_log)
    if ((n < 0) || (n >= 64))
       n = 0; /* Silence GCC warnings... */
 
-   JSON_Writer_WriteSpace(context.writer, 2);
-   JSON_Writer_WriteString(context.writer, "last_played",
-         STRLEN_CONST("last_played"), JSON_UTF8);
-   JSON_Writer_WriteColon(context.writer);
-   JSON_Writer_WriteSpace(context.writer, 1);
-   JSON_Writer_WriteString(context.writer, value_string,
-         strlen(value_string), JSON_UTF8);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_spaces(writer, 2);
+   rjsonwriter_add_string(writer, "last_played");
+   rjsonwriter_add_colon(writer);
+   rjsonwriter_add_space(writer);
+   rjsonwriter_add_string(writer, value_string);
+   rjsonwriter_add_newline(writer);
 
    /* > Finalise */
-   JSON_Writer_WriteEndObject(context.writer);
-   JSON_Writer_WriteNewLine(context.writer);
+   rjsonwriter_add_end_object(writer);
+   rjsonwriter_add_newline(writer);
 
    /* Free JSON writer */
-   JSON_Writer_Free(context.writer);
+   if (!rjsonwriter_free(writer))
+   {
+      RARCH_ERR("Error writing runtime log file: %s\n", runtime_log->path);
+   }
 
 end:
    /* Close log file */
diff --git a/samples/tasks/database/Makefile b/samples/tasks/database/Makefile
index d14eff8565..e54196a607 100644
--- a/samples/tasks/database/Makefile
+++ b/samples/tasks/database/Makefile
@@ -114,8 +114,7 @@ SOURCES_C := \
 	$(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \
 	$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
 	$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
-	$(LIBRETRO_COMM_DIR)/formats/json/jsonsax.c \
-	$(LIBRETRO_COMM_DIR)/formats/json/jsonsax_full.c \
+	$(LIBRETRO_COMM_DIR)/formats/json/rjson.c \
 	$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \
 	$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
 	$(LIBRETRO_COMM_DIR)/queues/task_queue.c \

From c0cb3373e3a2cd6e0caf8e2e618f9946aec6d196 Mon Sep 17 00:00:00 2001
From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com>
Date: Mon, 12 Oct 2020 04:30:45 +0900
Subject: [PATCH 2/3] Fixes for CXX_BUILD

---
 libretro-common/formats/json/rjson.c | 29 +++++++++++++++++-----------
 1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/libretro-common/formats/json/rjson.c b/libretro-common/formats/json/rjson.c
index a71233f9e5..538ecb4352 100644
--- a/libretro-common/formats/json/rjson.c
+++ b/libretro-common/formats/json/rjson.c
@@ -719,7 +719,8 @@ enum rjson_type rjson_next(rjson_t *json)
                   continue;
                }
                json->input_p = p;
-               return _rjson_error_token(json, "expected ':' not %s after member name", tok);
+               return _rjson_error_token(json,
+                     "expected ':' not %s after member name", (enum _rjson_token)tok);
             }
             if (passed_token)
             {
@@ -749,7 +750,8 @@ enum rjson_type rjson_next(rjson_t *json)
                continue;
             }
             json->input_p = p;
-            return _rjson_error_token(json, "expected ',' or '}' not %s after member value", tok);
+            return _rjson_error_token(json,
+                  "expected ',' or '}' not %s after member value", (enum _rjson_token)tok);
          }
          else if (stack->type == RJSON_ARRAY)
          {
@@ -769,7 +771,8 @@ enum rjson_type rjson_next(rjson_t *json)
                continue;
             }
             json->input_p = p;
-            return _rjson_error_token(json, "expected ',' or ']' not %s in array", tok);
+            return _rjson_error_token(json,
+                  "expected ',' or ']' not %s in array", (enum _rjson_token)tok);
          }
          else
          {
@@ -781,7 +784,8 @@ enum rjson_type rjson_next(rjson_t *json)
             if (tok == _rJSON_TOK_EOF)
                return RJSON_DONE;
             if (!(json->option_flags & RJSON_OPTION_ALLOW_TRAILING_DATA))
-               return _rjson_error_token(json, "expected end of stream instead of %s", tok);
+               return _rjson_error_token(json,
+                     "expected end of stream instead of %s", (enum _rjson_token)tok);
             json->input_p--;
             return RJSON_DONE;
          }
@@ -806,7 +810,8 @@ enum rjson_type rjson_next(rjson_t *json)
             return _rjson_read_name(json, "alse", RJSON_FALSE);
          else if ((tok == _rJSON_TOK_NULL))
             return _rjson_read_name(json, "ull", RJSON_NULL);
-         else return _rjson_error_token(json, "unexpected %s in value", tok);
+         else return _rjson_error_token(json,
+               "unexpected %s in value", (enum _rjson_token)tok);
       }
    }
    return RJSON_ERROR;
@@ -992,12 +997,12 @@ bool rjson_check_context(rjson_t *json, unsigned int depth, ...)
 {
    va_list ap;
    const struct _rjson_stack *stack = json->stack, *stack_top = json->stack_top;
-   if (stack_top - stack != depth)
+   if ((unsigned int)(stack_top - stack) != depth)
       return false;
    va_start(ap, depth);
    while (++stack <= stack_top)
    {
-      if (va_arg(ap, enum rjson_type) == stack->type) continue;
+      if (va_arg(ap, int) == (int)stack->type) continue;
       va_end(ap);
       return false;
    }
@@ -1157,9 +1162,9 @@ struct rjsonwriter
    rjsonwriter_io_t io;
    void *user_data;
 
+   const char* error_text;
    char option_flags;
    char decimal_sep;
-   char* error_text;
 
    char inline_buf[1024];
 };
@@ -1248,7 +1253,7 @@ void rjsonwriter_raw(rjsonwriter_t *writer, const char *buf, int len)
       if (add > (unsigned int)len) add = (unsigned int)len;
       memcpy(writer->buf + writer->buf_num, buf, add);
       writer->buf_num += add;
-      if (len == add) return;
+      if ((unsigned int)len == add) return;
       rjsonwriter_flush(writer);
       len -= add;
       buf += add;
@@ -1310,7 +1315,8 @@ void rjsonwriter_add_string(rjsonwriter_t *writer, const char *value)
       if (c < 0x20 || c == '\"' || c == '\\' ||
             (c == '/' && p > value + 1 && p[-2] == '<'))
       {
-         char* esc, esc_buf[8], esc_len = 2;
+         char esc_buf[8], esc_len = 2;
+         const char* esc;
          if (raw != p - 1)
             rjsonwriter_raw(writer, raw, (int)(p - 1 - raw));
          switch (c)
@@ -1324,7 +1330,8 @@ void rjsonwriter_add_string(rjsonwriter_t *writer, const char *value)
             case '\\': esc = "\\\\"; break;
             case '/': esc = "\\/"; break;
             default:
-               snprintf((esc = esc_buf), sizeof(esc_buf), "\\u%04x", c);
+               snprintf(esc_buf, sizeof(esc_buf), "\\u%04x", c);
+               esc = esc_buf;
                esc_len = 6;
          }
          rjsonwriter_raw(writer, esc, esc_len);

From b90ce9b451c3cc848c9c1e4e9f45f7d9b7a24b7c Mon Sep 17 00:00:00 2001
From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com>
Date: Mon, 12 Oct 2020 05:25:12 +0900
Subject: [PATCH 3/3] Fixes for Clang warnings

---
 libretro-common/formats/json/rjson.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/libretro-common/formats/json/rjson.c b/libretro-common/formats/json/rjson.c
index 538ecb4352..9251e0d36a 100644
--- a/libretro-common/formats/json/rjson.c
+++ b/libretro-common/formats/json/rjson.c
@@ -580,7 +580,7 @@ static enum rjson_type _rjson_read_name(rjson_t *json, const char *pattern, enum
    _rjson_char_t c;
    const char *p;
    for (p = pattern; *p; p++)
-      if (*p != (c = _rjson_char_get(json)))
+      if ((_rjson_char_t)*p != (c = _rjson_char_get(json)))
          return _rjson_error_char(json, "unexpected %s in value", c);
    return type;
 }
@@ -796,19 +796,19 @@ enum rjson_type rjson_next(rjson_t *json)
          stack->count++;
          /* This is optimal when there are many strings, otherwise a switch statement
           * or a function pointer table is better (depending on compiler/cpu) */
-         if      ((tok == _rJSON_TOK_STRING))
+         if      (tok == _rJSON_TOK_STRING)
             return _rjson_read_string(json);
-         else if ((tok == _rJSON_TOK_NUMBER))
+         else if (tok == _rJSON_TOK_NUMBER)
             return _rjson_read_number(json);
-         else if ((tok == _rJSON_TOK_OBJECT))
+         else if (tok == _rJSON_TOK_OBJECT)
             return _rjson_push_stack(json, _rJSON_TOK_OBJECT);
-         else if ((tok == _rJSON_TOK_ARRAY))
+         else if (tok == _rJSON_TOK_ARRAY)
             return _rjson_push_stack(json, _rJSON_TOK_ARRAY);
-         else if ((tok == _rJSON_TOK_TRUE))
+         else if (tok == _rJSON_TOK_TRUE)
             return _rjson_read_name(json, "rue", RJSON_TRUE);
-         else if ((tok == _rJSON_TOK_FALSE))
+         else if (tok == _rJSON_TOK_FALSE)
             return _rjson_read_name(json, "alse", RJSON_FALSE);
-         else if ((tok == _rJSON_TOK_NULL))
+         else if (tok == _rJSON_TOK_NULL)
             return _rjson_read_name(json, "ull", RJSON_NULL);
          else return _rjson_error_token(json,
                "unexpected %s in value", (enum _rjson_token)tok);