[pango/serializer-rewrite: 2/2] serializer: Replace json-glib




commit 7516091ae141ced79b3c38d668eec121cc84bf12
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Nov 29 23:53:12 2021 -0500

    serializer: Replace json-glib
    
    Replace json-glib by a homegrown json parser/printer.

 meson.build                        |    5 -
 pango/json/gtkjsonparser.c         | 1131 ++++++++++++++++++++++++
 pango/json/gtkjsonparserprivate.h  |   66 ++
 pango/json/gtkjsonprinter.c        |  405 +++++++++
 pango/json/gtkjsonprinterprivate.h |   79 ++
 pango/meson.build                  |    2 +
 pango/serializer.c                 | 1656 +++++++++++++++++-------------------
 7 files changed, 2484 insertions(+), 860 deletions(-)
---
diff --git a/meson.build b/meson.build
index c91ddb52..7afe6193 100644
--- a/meson.build
+++ b/meson.build
@@ -232,7 +232,6 @@ harfbuzz_req_version = '>= 2.6.0'
 fontconfig_req_version = '>= 2.13.0'
 xft_req_version = '>= 2.0.0'
 cairo_req_version = '>= 1.12.10'
-json_glib_version = '>= 1.6.0'
 
 # libm
 mathlib_dep = cc.find_library('m', required: false)
@@ -252,10 +251,6 @@ fribidi_dep = dependency('fribidi', version: fribidi_req_version,
                          default_options: ['docs=false'])
 pango_deps += fribidi_dep
 
-json_glib_dep = dependency('json-glib-1.0', version: json_glib_version,
-                           fallback: ['json-glib', 'json_glib_dep'])
-pango_deps += json_glib_dep
-
 thai_dep = dependency('libthai', version: libthai_req_version, required: get_option('libthai'))
 if thai_dep.found()
   pango_conf.set('HAVE_LIBTHAI', 1)
diff --git a/pango/json/gtkjsonparser.c b/pango/json/gtkjsonparser.c
new file mode 100644
index 00000000..a2acb043
--- /dev/null
+++ b/pango/json/gtkjsonparser.c
@@ -0,0 +1,1131 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#include "config.h"
+
+#include "gtkjsonparserprivate.h"
+
+typedef struct _GtkJsonBlock GtkJsonBlock;
+
+typedef enum {
+  GTK_JSON_BLOCK_TOPLEVEL,
+  GTK_JSON_BLOCK_OBJECT,
+  GTK_JSON_BLOCK_ARRAY,
+} GtkJsonBlockType;
+
+struct _GtkJsonBlock
+{
+  GtkJsonBlockType type;
+  const guchar *value; /* start of current value to be consumed by external code */
+  const guchar *member_name; /* name of current value, only used for object types */
+  gsize index; /* index of the current element */
+};
+
+struct _GtkJsonParser
+{
+  GBytes *bytes;
+  const guchar *reader; /* current read head, pointing as far as we've read */
+  const guchar *end; /* pointer after end of data we're reading */
+
+  GError *error; /* if an error has happened, it's stored here. Errors aren't recoverable. */
+
+  GtkJsonBlock *block; /* current block */
+  GtkJsonBlock *blocks; /* blocks array */
+  GtkJsonBlock *blocks_end; /* blocks array */
+  GtkJsonBlock blocks_preallocated[128]; /* preallocated */
+};
+
+typedef enum {
+  WHITESPACE     = (1 << 4),
+  STRING_ELEMENT = (1 << 5),
+  STRING_MARKER  = (1 << 6),
+} JsonCharacterType;
+
+#define JSON_CHARACTER_NODE_MASK ((1 << 4) - 1)
+
+static const guchar json_character_table[256] = {
+  ['\t'] = WHITESPACE,
+  ['\r'] = WHITESPACE,
+  ['\n'] = WHITESPACE,
+  [' ']  = WHITESPACE | STRING_ELEMENT,
+  ['!']  = STRING_ELEMENT,
+  ['"']  = GTK_JSON_STRING | STRING_MARKER,
+  ['#']  = STRING_ELEMENT,
+  ['$']  = STRING_ELEMENT,
+  ['%']  = STRING_ELEMENT,
+  ['&']  = STRING_ELEMENT,
+  ['\''] = STRING_ELEMENT,
+  ['(']  = STRING_ELEMENT,
+  [')']  = STRING_ELEMENT,
+  ['*']  = STRING_ELEMENT,
+  ['+']  = STRING_ELEMENT,
+  [',']  = STRING_ELEMENT,
+  ['-']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['.']  = STRING_ELEMENT,
+  ['/']  = STRING_ELEMENT,
+  ['0']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['1']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['2']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['3']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['4']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['5']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['6']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['7']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['8']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  ['9']  = GTK_JSON_NUMBER | STRING_ELEMENT,
+  [':']  = STRING_ELEMENT,
+  [';']  = STRING_ELEMENT,
+  ['<']  = STRING_ELEMENT,
+  ['=']  = STRING_ELEMENT,
+  ['>']  = STRING_ELEMENT,
+  ['?']  = STRING_ELEMENT,
+  ['@']  = STRING_ELEMENT,
+  ['A']  = STRING_ELEMENT,
+  ['B']  = STRING_ELEMENT,
+  ['C']  = STRING_ELEMENT,
+  ['D']  = STRING_ELEMENT,
+  ['E']  = STRING_ELEMENT,
+  ['F']  = STRING_ELEMENT,
+  ['G']  = STRING_ELEMENT,
+  ['H']  = STRING_ELEMENT,
+  ['I']  = STRING_ELEMENT,
+  ['J']  = STRING_ELEMENT,
+  ['K']  = STRING_ELEMENT,
+  ['L']  = STRING_ELEMENT,
+  ['M']  = STRING_ELEMENT,
+  ['N']  = STRING_ELEMENT,
+  ['O']  = STRING_ELEMENT,
+  ['P']  = STRING_ELEMENT,
+  ['Q']  = STRING_ELEMENT,
+  ['R']  = STRING_ELEMENT,
+  ['S']  = STRING_ELEMENT,
+  ['T']  = STRING_ELEMENT,
+  ['U']  = STRING_ELEMENT,
+  ['V']  = STRING_ELEMENT,
+  ['W']  = STRING_ELEMENT,
+  ['X']  = STRING_ELEMENT,
+  ['Y']  = STRING_ELEMENT,
+  ['Z']  = STRING_ELEMENT,
+  ['[']  = GTK_JSON_ARRAY | STRING_ELEMENT,
+  ['\\'] = STRING_MARKER,
+  [']']  = STRING_ELEMENT,
+  ['^']  = STRING_ELEMENT,
+  ['_']  = STRING_ELEMENT,
+  ['`']  = STRING_ELEMENT,
+  ['a']  = STRING_ELEMENT,
+  ['b']  = STRING_ELEMENT,
+  ['c']  = STRING_ELEMENT,
+  ['d']  = STRING_ELEMENT,
+  ['e']  = STRING_ELEMENT,
+  ['f']  = GTK_JSON_BOOLEAN | STRING_ELEMENT,
+  ['g']  = STRING_ELEMENT,
+  ['h']  = STRING_ELEMENT,
+  ['i']  = STRING_ELEMENT,
+  ['j']  = STRING_ELEMENT,
+  ['k']  = STRING_ELEMENT,
+  ['l']  = STRING_ELEMENT,
+  ['m']  = STRING_ELEMENT,
+  ['n']  = GTK_JSON_NULL | STRING_ELEMENT,
+  ['o']  = STRING_ELEMENT,
+  ['p']  = STRING_ELEMENT,
+  ['q']  = STRING_ELEMENT,
+  ['r']  = STRING_ELEMENT,
+  ['s']  = STRING_ELEMENT,
+  ['t']  = GTK_JSON_BOOLEAN | STRING_ELEMENT,
+  ['u']  = STRING_ELEMENT,
+  ['v']  = STRING_ELEMENT,
+  ['w']  = STRING_ELEMENT,
+  ['x']  = STRING_ELEMENT,
+  ['y']  = STRING_ELEMENT,
+  ['z']  = STRING_ELEMENT,
+  ['{']  = GTK_JSON_OBJECT | STRING_ELEMENT,
+  ['|']  = STRING_ELEMENT,
+  ['}']  = STRING_ELEMENT,
+  ['~']  = STRING_ELEMENT,
+  [127]  = STRING_ELEMENT,
+};
+
+static const guchar *
+json_skip_characters (const guchar      *start,
+                      const guchar      *end,
+                      JsonCharacterType  type)
+{
+  const guchar *s;
+
+  for (s = start; s < end; s++)
+    {
+      if (!(json_character_table[*s] & type))
+        break;
+    }
+  return s;
+}
+
+static const guchar *
+json_find_character (const guchar      *start,
+                     JsonCharacterType  type)
+{
+  const guchar *s;
+
+  for (s = start; ; s++)
+    {
+      if ((json_character_table[*s] & type))
+        break;
+    }
+  return s;
+}
+
+static void
+gtk_json_parser_value_error (GtkJsonParser *self,
+                             const char  *format,
+                             ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_value_error (GtkJsonParser *self,
+                             const char  *format,
+                             ...)
+{
+  va_list args;
+
+  if (self->error)
+    return;
+
+  va_start (args, format);
+  self->error = g_error_new_valist (G_FILE_ERROR,
+                                    G_FILE_ERROR_FAILED,
+                                    format, args);
+  va_end (args);
+}
+
+static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+                              const char  *format,
+                              ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+                              const char  *format,
+                              ...)
+{
+  va_list args;
+
+  if (self->error)
+    return;
+
+  va_start (args, format);
+  self->error = g_error_new_valist (G_FILE_ERROR,
+                                    G_FILE_ERROR_FAILED,
+                                    format, args);
+  va_end (args);
+}
+
+static gboolean
+gtk_json_parser_is_eof (GtkJsonParser *self)
+{
+  return self->reader >= self->end || *self->reader == '\0';
+}
+
+static gsize
+gtk_json_parser_remaining (GtkJsonParser *self)
+{
+  g_return_val_if_fail (self->reader <= self->end, 0);
+
+  return self->end - self->reader;
+}
+
+static void
+gtk_json_parser_skip_whitespace (GtkJsonParser *self)
+{
+  self->reader = json_skip_characters (self->reader, self->end, WHITESPACE);
+}
+
+static gboolean
+gtk_json_parser_has_char (GtkJsonParser *self,
+                          char           c)
+{
+  return gtk_json_parser_remaining (self) && *self->reader == c;
+}
+
+static gboolean
+gtk_json_parser_try_char (GtkJsonParser *self,
+                          char           c)
+{
+  if (!gtk_json_parser_has_char (self, c))
+    return FALSE;
+
+  self->reader++;
+  return TRUE;
+}
+
+static gboolean
+gtk_json_parser_try_identifier_len (GtkJsonParser *self,
+                                    const char    *ident,
+                                    gsize          len)
+{
+  if (gtk_json_parser_remaining (self) < len)
+    return FALSE;
+
+  if (memcmp (self->reader, ident, len) != 0)
+    return FALSE;
+
+  self->reader += len;
+  return TRUE;
+}
+
+#define gtk_json_parser_try_identifier(parser, ident) gtk_json_parser_try_identifier_len(parser, ident, 
strlen(ident))
+
+/*
+ * decode_utf16_surrogate_pair:
+ * @first: the first UTF-16 code point
+ * @second: the second UTF-16 code point
+ *
+ * Decodes a surrogate pair of UTF-16 code points into the equivalent
+ * Unicode code point.
+ *
+ * If the code points are not valid, 0 is returned.
+ *
+ * Returns: the Unicode code point equivalent to the surrogate pair
+ */
+static inline gunichar
+decode_utf16_surrogate_pair (gunichar first,
+                             gunichar second)
+{
+  if (0xd800 > first || first > 0xdbff ||
+      0xdc00 > second || second > 0xdfff)
+    return 0;
+
+  return 0x10000
+       | (first & 0x3ff) << 10
+       | (second & 0x3ff);
+}
+
+static gsize
+gtk_json_unescape_char (const guchar *json_escape,
+                        char          out_data[6],
+                        gsize        *out_len)
+{
+  switch (json_escape[1])
+    {
+    case '"':
+    case '\\':
+    case '/':
+      out_data[0] = json_escape[1];
+      *out_len = 1;
+      return 2;
+    case 'b':
+      out_data[0] = '\b';
+      *out_len = 1;
+      return 2;
+    case 'f':
+      out_data[0] = '\f';
+      *out_len = 1;
+      return 2;
+    case 'n':
+      out_data[0] = '\n';
+      *out_len = 1;
+      return 2;
+    case 'r':
+      out_data[0] = '\r';
+      *out_len = 1;
+      return 2;
+    case 't':
+      out_data[0] = '\t';
+      *out_len = 1;
+      return 2;
+    case 'u':
+      {
+        gunichar unichar = (g_ascii_xdigit_value (json_escape[2]) << 12) |
+                           (g_ascii_xdigit_value (json_escape[3]) <<  8) |
+                           (g_ascii_xdigit_value (json_escape[4]) <<  4) |
+                           (g_ascii_xdigit_value (json_escape[5]));
+        gsize result = 6;
+
+        /* resolve UTF-16 surrogates for Unicode characters not in the BMP,
+         * as per ECMA 404, § 9, "String"
+         */
+        if (g_unichar_type (unichar) == G_UNICODE_SURROGATE)
+          {
+            unichar = decode_utf16_surrogate_pair (unichar,
+                                                   (g_ascii_xdigit_value (json_escape[8])  << 12) |
+                                                   (g_ascii_xdigit_value (json_escape[9])  <<  8) |
+                                                   (g_ascii_xdigit_value (json_escape[10]) <<  4) |
+                                                   (g_ascii_xdigit_value (json_escape[11])));
+            result += 6;
+          }
+        *out_len = g_unichar_to_utf8 (unichar, out_data);
+        return result;
+        }
+    default:
+      g_assert_not_reached ();
+      return 0;
+    }
+}
+                   
+/* The escaped string MUST be valid json, so it must begin
+ * with " and end with " and must not contain any invalid
+ * escape codes.
+ * This function is meant to be fast
+ */
+static char *
+gtk_json_unescape_string (const guchar *escaped)
+{
+  char buf[6];
+  gsize buf_size;
+  GString *string;
+  const guchar *last, *s;
+
+  string = NULL;
+
+  g_assert (*escaped == '"');
+  last = escaped + 1;
+  for (s = json_find_character (last, STRING_MARKER);
+       *s != '"';
+       s = json_find_character (last, STRING_MARKER))
+    {
+      g_assert (*s == '\\');
+      if (string == NULL)
+        string = g_string_new (NULL);
+      g_string_append_len (string, (const char *) last, s - last);
+      last = s + gtk_json_unescape_char (s, buf, &buf_size);
+      g_string_append_len (string, buf, buf_size);
+    }
+
+  if (string)
+    {
+      g_string_append_len (string, (const char *) last, s - last);
+      return g_string_free (string, FALSE);
+    }
+  else
+    {
+      return g_strndup ((const char *) last, s - last);
+    }
+}
+
+static gboolean
+gtk_json_parser_parse_string (GtkJsonParser *self)
+{
+  if (!gtk_json_parser_try_char (self, '"'))
+    {
+      gtk_json_parser_syntax_error (self, "Not a string");
+      return FALSE;
+    }
+
+  self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT);
+
+  while (gtk_json_parser_remaining (self))
+    {
+      if (*self->reader < 0x20)
+        {
+          gtk_json_parser_syntax_error (self, "Disallowed control character in string literal");
+          return FALSE;
+        }
+      else if (*self->reader > 127)
+        {
+          gunichar c = g_utf8_get_char_validated ((const char *) self->reader, gtk_json_parser_remaining 
(self));
+          if (c == (gunichar) -2 || c == (gunichar) -1)
+            {
+              gtk_json_parser_syntax_error (self, "Invalid UTF-8");
+              return FALSE;
+            }
+          self->reader = (const guchar *) g_utf8_next_char ((const char *) self->reader);
+        }
+      else if (*self->reader == '"')
+        {
+          self->reader++;
+          return TRUE;
+        }
+      else if (*self->reader == '\\')
+        {
+          if (gtk_json_parser_remaining (self) < 2)
+            goto end;
+          self->reader++;
+          switch (*self->reader)
+            {
+            case '"':
+            case '\\':
+            case '/':
+            case 'b':
+            case 'f':
+            case 'n':
+            case 'r':
+            case 't':
+              break;
+
+            case 'u':
+              /* lots of work necessary to validate the unicode escapes here */
+              if (gtk_json_parser_remaining (self) < 5 ||
+                  !g_ascii_isxdigit (self->reader[1]) ||
+                  !g_ascii_isxdigit (self->reader[2]) ||
+                  !g_ascii_isxdigit (self->reader[3]) ||
+                  !g_ascii_isxdigit (self->reader[4]))
+                {
+                  gtk_json_parser_syntax_error (self, "Invalid Unicode escape sequence");
+                  return FALSE;
+                }
+              else
+                {
+                  gunichar unichar = (g_ascii_xdigit_value (self->reader[1]) << 12) |
+                                     (g_ascii_xdigit_value (self->reader[2]) <<  8) |
+                                     (g_ascii_xdigit_value (self->reader[3]) <<  4) |
+                                     (g_ascii_xdigit_value (self->reader[4]));
+
+                  self->reader += 4;
+                  /* resolve UTF-16 surrogates for Unicode characters not in the BMP,
+                   * as per ECMA 404, § 9, "String"
+                   */
+                  if (g_unichar_type (unichar) == G_UNICODE_SURROGATE)
+                    {
+                      if (gtk_json_parser_remaining (self) >= 7 &&
+                          self->reader[1] == '\\' &&
+                          self->reader[2] == 'u' &&
+                          g_ascii_isxdigit (self->reader[3]) &&
+                          g_ascii_isxdigit (self->reader[4]) &&
+                          g_ascii_isxdigit (self->reader[5]) &&
+                          g_ascii_isxdigit (self->reader[6]))
+                        {
+                          unichar = decode_utf16_surrogate_pair (unichar,
+                                                                 (g_ascii_xdigit_value (self->reader[3]) << 
12) |
+                                                                 (g_ascii_xdigit_value (self->reader[4]) <<  
8) |
+                                                                 (g_ascii_xdigit_value (self->reader[5]) <<  
4) |
+                                                                 (g_ascii_xdigit_value (self->reader[6])));
+                          self->reader += 6;
+                        }
+                      else
+                        {
+                          unichar = 0;
+                        }
+                    }
+
+                  if (unichar == 0)
+                    {
+                      gtk_json_parser_syntax_error (self, "Invalid UTF-16 surrogate pair");
+                      return FALSE;
+                    }
+                }
+              break;
+            default:
+              gtk_json_parser_syntax_error (self, "Unknown escape sequence");
+              return FALSE;
+            }
+          self->reader++;
+        }
+
+      self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT);
+    }
+
+end:
+  gtk_json_parser_syntax_error (self, "Unterminated string literal");
+  return FALSE;
+}
+
+static gboolean
+gtk_json_parser_parse_number (GtkJsonParser *self)
+{
+  /* sign */
+  gtk_json_parser_try_char (self, '-');
+
+  /* integer part */
+  if (!gtk_json_parser_try_char (self, '0'))
+    {
+      if (gtk_json_parser_is_eof (self) ||
+          !g_ascii_isdigit (*self->reader))
+        goto out;
+
+      self->reader++;
+
+      while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader))
+        self->reader++;
+    }
+
+  /* fractional part */
+  if (gtk_json_parser_remaining (self) >= 2 && *self->reader == '.' && g_ascii_isdigit (self->reader[1]))
+    {
+      self->reader += 2;
+
+      while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader))
+        self->reader++;
+    }
+
+  /* exponent */
+  if (gtk_json_parser_remaining (self) >= 2 && (self->reader[0] == 'e' || self->reader[0] == 'E') &&
+      (g_ascii_isdigit (self->reader[1]) ||
+       (gtk_json_parser_remaining (self) >= 3 && (self->reader[1] == '+' || self->reader[1] == '-') && 
g_ascii_isdigit (self->reader[2]))))
+    {
+      self->reader += 2;
+
+      while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader))
+        self->reader++;
+    }
+  return TRUE;
+
+out:
+  gtk_json_parser_syntax_error (self, "Not a valid number");
+  return FALSE;
+}
+
+static gboolean
+gtk_json_parser_parse_value (GtkJsonParser *self)
+{
+  if (gtk_json_parser_is_eof (self))
+    {
+      gtk_json_parser_syntax_error (self, "Unexpected end of document");
+      return FALSE;
+    }
+
+  switch (json_character_table[*self->block->value] & JSON_CHARACTER_NODE_MASK)
+  {
+    case GTK_JSON_STRING:
+      return gtk_json_parser_parse_string (self);
+    
+    case GTK_JSON_NUMBER:
+      return gtk_json_parser_parse_number (self);
+
+    case GTK_JSON_NULL:
+      if (gtk_json_parser_try_identifier (self, "null"))
+        return TRUE;
+      break;
+
+    case GTK_JSON_BOOLEAN:
+      if (gtk_json_parser_try_identifier (self, "true") ||
+          gtk_json_parser_try_identifier (self, "false"))
+        return TRUE;
+      break;
+
+    case GTK_JSON_OBJECT:
+    case GTK_JSON_ARRAY:
+      /* don't preparse objects */
+      return TRUE;
+
+    default:
+      break;
+  }
+
+  gtk_json_parser_syntax_error (self, "Expected a value");
+  return FALSE;
+}
+
+static void
+gtk_json_parser_push_block (GtkJsonParser    *self,
+                            GtkJsonBlockType  type)
+{
+  self->block++;
+  if (self->block == self->blocks_end)
+    {
+      gsize old_size = self->blocks_end - self->blocks;
+      gsize new_size = old_size + 128;
+
+      if (self->blocks == self->blocks_preallocated)
+        {
+          self->blocks = g_new (GtkJsonBlock, new_size);
+          memcpy (self->blocks, self->blocks_preallocated, sizeof (GtkJsonBlock) * G_N_ELEMENTS 
(self->blocks_preallocated));
+        }
+      else
+        {
+          self->blocks = g_renew (GtkJsonBlock, self->blocks, new_size);
+        }
+      self->blocks_end = self->blocks + new_size;
+      self->block = self->blocks + old_size;
+    }
+
+  self->block->type = type;
+  self->block->member_name = 0;
+  self->block->value = 0;
+  self->block->index = 0;
+}
+                     
+static void
+gtk_json_parser_pop_block (GtkJsonParser *self)
+{
+  g_assert (self->block > self->blocks);
+  self->block--;
+}
+
+GtkJsonParser *
+gtk_json_parser_new_for_string (const char *string,
+                                gssize      size)
+{
+  GtkJsonParser *self;
+  GBytes *bytes;
+
+  bytes = g_bytes_new (string, size >= 0 ? size : strlen (string));
+
+  self = gtk_json_parser_new_for_bytes (bytes);
+
+  g_bytes_unref (bytes);
+
+  return self;
+}
+
+GtkJsonParser *
+gtk_json_parser_new_for_bytes (GBytes *bytes)
+{
+  GtkJsonParser *self;
+  gsize size;
+
+  g_return_val_if_fail (bytes != NULL, NULL);
+
+  self = g_slice_new0 (GtkJsonParser);
+
+  self->bytes = g_bytes_ref (bytes);
+  self->reader = g_bytes_get_data (bytes, &size);
+  self->end = self->reader + size;
+
+  self->blocks = self->blocks_preallocated;
+  self->blocks_end = self->blocks + G_N_ELEMENTS (self->blocks_preallocated);
+  self->block = self->blocks;
+  self->block->type = GTK_JSON_BLOCK_TOPLEVEL;
+
+  gtk_json_parser_skip_whitespace (self);
+  self->block->value = self->reader;
+  gtk_json_parser_parse_value (self);
+
+  return self;
+}
+
+void
+gtk_json_parser_free (GtkJsonParser *self)
+{
+  if (self == NULL)
+    return;
+
+  g_bytes_unref (self->bytes);
+
+  if (self->blocks != self->blocks_preallocated)
+    g_free (self->blocks);
+
+  g_slice_free (GtkJsonParser, self);
+}
+
+static gboolean
+gtk_json_parser_skip_block (GtkJsonParser *self)
+{
+  if (self->reader != self->block->value)
+    return TRUE;
+
+  if (*self->reader == '{')
+    {
+      return gtk_json_parser_start_object (self) &&
+             gtk_json_parser_end (self);
+    }
+  else if (*self->reader == '[')
+    {
+      return gtk_json_parser_start_array (self) &&
+             gtk_json_parser_end (self);
+    }
+  else
+    {
+      g_assert_not_reached ();
+      return FALSE;
+    }
+}
+
+gboolean
+gtk_json_parser_next (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (self->block->value == NULL)
+    return FALSE;
+
+  if (!gtk_json_parser_skip_block (self))
+    {
+      g_assert (self->error);
+      return FALSE;
+    }
+
+  switch (self->block->type)
+    {
+    case GTK_JSON_BLOCK_TOPLEVEL:
+      gtk_json_parser_skip_whitespace (self);
+      if (gtk_json_parser_is_eof (self))
+        {
+          self->block->value = NULL;
+          if (gtk_json_parser_remaining (self))
+            {
+              gtk_json_parser_syntax_error (self, "Unexpected nul byte in document");
+            }
+        }
+      else
+        {
+          gtk_json_parser_syntax_error (self, "Data at end of document");
+        }
+      return FALSE;
+
+    case GTK_JSON_BLOCK_OBJECT:
+      gtk_json_parser_skip_whitespace (self);
+      if (gtk_json_parser_is_eof (self))
+        {
+          gtk_json_parser_syntax_error (self, "Unexpected end of document");
+          self->block->member_name = NULL;
+          self->block->value = NULL;
+        }
+      if (gtk_json_parser_has_char (self, '}'))
+        {
+          self->block->member_name = NULL;
+          self->block->value = NULL;
+          return FALSE;
+        }
+      if (!gtk_json_parser_try_char (self, ','))
+        {
+          gtk_json_parser_syntax_error (self, "Expected a ',' to separate object members");
+          return FALSE;
+        }
+      gtk_json_parser_skip_whitespace (self);
+      self->block->member_name = self->reader;
+
+      if (!gtk_json_parser_parse_string (self))
+        return FALSE;
+      gtk_json_parser_skip_whitespace (self);
+      if (!gtk_json_parser_try_char (self, ':'))
+        {
+          gtk_json_parser_syntax_error (self, "Missing ':' after member name");
+          return FALSE;
+        }
+
+      gtk_json_parser_skip_whitespace (self);
+      self->block->value = self->reader;
+      if (!gtk_json_parser_parse_value (self))
+        return FALSE;
+      break;
+
+    case GTK_JSON_BLOCK_ARRAY:
+      gtk_json_parser_skip_whitespace (self);
+      if (gtk_json_parser_is_eof (self))
+        {
+          gtk_json_parser_syntax_error (self, "Unexpected end of document");
+          self->block->member_name = NULL;
+          self->block->value = NULL;
+        }
+      if (gtk_json_parser_has_char (self, ']'))
+        {
+          self->block->value = NULL;
+          return FALSE;
+        }
+
+      if (!gtk_json_parser_try_char (self, ','))
+        {
+          gtk_json_parser_syntax_error (self, "Expected a ',' to separate array members");
+          return FALSE;
+        }
+
+      gtk_json_parser_skip_whitespace (self);
+      self->block->value = self->reader;
+      if (!gtk_json_parser_parse_value (self))
+        return FALSE;
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return TRUE;
+}
+
+GtkJsonNode
+gtk_json_parser_get_node (GtkJsonParser *self)
+{
+  if (self->error)
+    return GTK_JSON_NONE;
+
+  if (self->block->value == NULL)
+    return GTK_JSON_NONE;
+
+  return (json_character_table[*self->block->value] & JSON_CHARACTER_NODE_MASK);
+}
+
+const GError *
+gtk_json_parser_get_error (GtkJsonParser *self)
+{
+  return self->error;
+}
+
+char *
+gtk_json_parser_get_member_name (GtkJsonParser *self)
+{
+  if (self->error)
+    return NULL;
+
+  if (self->block->type != GTK_JSON_BLOCK_OBJECT)
+    return NULL;
+
+  if (self->block->member_name == NULL)
+    return NULL;
+
+  return gtk_json_unescape_string (self->block->member_name);
+}
+
+gssize
+gtk_json_parser_select_member (GtkJsonParser      *self,
+                               const char * const *options)
+{
+  char *member_name;
+  gssize i;
+
+  member_name = gtk_json_parser_get_member_name (self);
+  if (member_name == NULL)
+    return -1;
+
+  for (i = 0; options[i]; i++)
+    {
+      if (strcmp (member_name, options[i]) == 0)
+        break;
+    }
+  if (options[i] == NULL)
+    i = -1;
+
+  g_free (member_name);
+
+  return i;
+}
+
+gboolean
+gtk_json_parser_get_boolean (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (self->block->value == NULL)
+    return FALSE;
+
+  if (*self->block->value == 't')
+    return TRUE;
+  else if (*self->block->value == 'f')
+    return FALSE;
+
+  gtk_json_parser_value_error (self, "Expected a boolean value");
+  return FALSE;
+}
+
+double
+gtk_json_parser_get_number (GtkJsonParser *self)
+{
+  double result;
+
+  if (self->error)
+    return 0;
+
+  if (self->block->value == NULL)
+    return 0;
+
+  if (!strchr ("-0123456789", *self->block->value))
+    {
+      gtk_json_parser_value_error (self, "Expected a number");
+      return 0;
+    }
+
+  errno = 0;
+  if (gtk_json_parser_remaining (self) == 0)
+    {
+      /* need terminated string here */
+      char *s = g_strndup ((const char *) self->block->value, self->reader - self->block->value);
+      result = g_ascii_strtod (s, NULL);
+      g_free (s);
+    }
+  else
+    {
+      result = g_ascii_strtod ((const char *) self->block->value, NULL);
+    }
+
+  if (errno)
+    {
+      if (errno == ERANGE)
+        gtk_json_parser_value_error (self, "Number out of range");
+      else
+        gtk_json_parser_value_error (self, "%s", g_strerror (errno));
+
+      return 0;
+    }
+
+  return result;
+}
+
+int
+gtk_json_parser_get_int (GtkJsonParser *self)
+{
+  int result;
+
+  if (self->error)
+    return 0;
+
+  if (self->block->value == NULL)
+    return 0;
+
+  if (!strchr ("-0123456789", *self->block->value))
+    {
+      gtk_json_parser_value_error (self, "Expected a number");
+      return 0;
+    }
+
+  errno = 0;
+  if (gtk_json_parser_remaining (self) == 0)
+    {
+      /* need terminated string here */
+      char *s = g_strndup ((const char *) self->block->value, self->reader - self->block->value);
+      result = g_ascii_strtod (s, NULL);
+      g_free (s);
+    }
+  else
+    {
+      result = (int) g_ascii_strtoll ((const char *) self->block->value, NULL, 10);
+    }
+
+  if (errno)
+    {
+      if (errno == ERANGE)
+        gtk_json_parser_value_error (self, "Number out of range");
+      else
+        gtk_json_parser_value_error (self, "%s", g_strerror (errno));
+
+      return 0;
+    }
+
+  return result;
+}
+
+#if 0
+int                     gtk_json_parser_get_int                 (GtkJsonParser          *self);
+guint                   gtk_json_parser_get_uint                (GtkJsonParser          *self);
+#endif
+
+char *
+gtk_json_parser_get_string (GtkJsonParser *self)
+{
+  if (self->error)
+    return g_strdup ("");
+
+  if (self->block->value == NULL)
+    return g_strdup ("");
+
+  if (*self->block->value != '"')
+    {
+      gtk_json_parser_value_error (self, "Expected a string");
+      return g_strdup ("");
+    }
+
+  return gtk_json_unescape_string (self->block->value);
+}
+
+gboolean
+gtk_json_parser_start_object (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (!gtk_json_parser_try_char (self, '{'))
+    {
+      gtk_json_parser_value_error (self, "Expected an object");
+      return FALSE;
+    }
+
+  gtk_json_parser_push_block (self, GTK_JSON_BLOCK_OBJECT);
+
+  gtk_json_parser_skip_whitespace (self);
+  if (gtk_json_parser_is_eof (self))
+    {
+      gtk_json_parser_syntax_error (self, "Unexpected end of document");
+      return FALSE;
+    }
+  if (gtk_json_parser_has_char (self, '}'))
+    return TRUE;
+  self->block->member_name = self->reader;
+
+  if (!gtk_json_parser_parse_string (self))
+    return FALSE;
+  gtk_json_parser_skip_whitespace (self);
+  if (!gtk_json_parser_try_char (self, ':'))
+    {
+      gtk_json_parser_syntax_error (self, "Missing ':' after member name");
+      return FALSE;
+    }
+
+  gtk_json_parser_skip_whitespace (self);
+  self->block->value = self->reader;
+  if (!gtk_json_parser_parse_value (self))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+gtk_json_parser_start_array (GtkJsonParser *self)
+{
+  if (self->error)
+    return FALSE;
+
+  if (!gtk_json_parser_try_char (self, '['))
+    {
+      gtk_json_parser_value_error (self, "Expected an array");
+      return FALSE;
+    }
+
+  gtk_json_parser_push_block (self, GTK_JSON_BLOCK_ARRAY);
+  gtk_json_parser_skip_whitespace (self);
+  if (gtk_json_parser_is_eof (self))
+    {
+      gtk_json_parser_syntax_error (self, "Unexpected end of document");
+      return FALSE;
+    }
+  if (gtk_json_parser_has_char (self, ']'))
+    {
+      self->block->value = NULL;
+      return TRUE;
+    }
+  self->block->value = self->reader;
+  if (!gtk_json_parser_parse_value (self))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+gtk_json_parser_end (GtkJsonParser *self)
+{
+  char bracket;
+
+  g_return_val_if_fail (self != NULL, FALSE);
+
+  while (gtk_json_parser_next (self));
+
+  if (self->error)
+    return FALSE;
+
+  switch (self->block->type)
+    {
+    case GTK_JSON_BLOCK_OBJECT:
+      bracket = '}';
+      break;
+    case GTK_JSON_BLOCK_ARRAY:
+      bracket = ']';
+      break;
+    case GTK_JSON_BLOCK_TOPLEVEL:
+    default:
+      g_return_val_if_reached (FALSE);
+    }
+
+  if (!gtk_json_parser_try_char (self, bracket))
+    {
+      gtk_json_parser_syntax_error (self, "No terminating '%c'", bracket);
+      return FALSE;
+    }
+
+  gtk_json_parser_pop_block (self);
+
+  return TRUE;
+}
+
diff --git a/pango/json/gtkjsonparserprivate.h b/pango/json/gtkjsonparserprivate.h
new file mode 100644
index 00000000..f3ff43fd
--- /dev/null
+++ b/pango/json/gtkjsonparserprivate.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_JSON_PARSER_H__
+#define __GTK_JSON_PARSER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+  GTK_JSON_NONE,
+  GTK_JSON_NULL,
+  GTK_JSON_BOOLEAN,
+  GTK_JSON_NUMBER,
+  GTK_JSON_STRING,
+  GTK_JSON_OBJECT,
+  GTK_JSON_ARRAY
+} GtkJsonNode;
+
+typedef struct _GtkJsonParser GtkJsonParser;
+
+GtkJsonParser *         gtk_json_parser_new_for_bytes           (GBytes                 *bytes);
+GtkJsonParser *         gtk_json_parser_new_for_string          (const char             *string,
+                                                                 gssize                  size);
+
+void                    gtk_json_parser_free                    (GtkJsonParser          *self);
+
+gboolean                gtk_json_parser_next                    (GtkJsonParser          *self);
+GtkJsonNode             gtk_json_parser_get_node                (GtkJsonParser          *self);
+const GError *          gtk_json_parser_get_error               (GtkJsonParser          *self) G_GNUC_PURE;
+char *                  gtk_json_parser_get_member_name         (GtkJsonParser          *self);
+gssize                  gtk_json_parser_select_member           (GtkJsonParser          *self,
+                                                                 const char * const     *options);
+
+gboolean                gtk_json_parser_get_boolean             (GtkJsonParser          *self);
+double                  gtk_json_parser_get_number              (GtkJsonParser          *self);
+int                     gtk_json_parser_get_int                 (GtkJsonParser          *self);
+guint                   gtk_json_parser_get_uint                (GtkJsonParser          *self);
+char *                  gtk_json_parser_get_string              (GtkJsonParser          *self);
+
+gboolean                gtk_json_parser_start_object            (GtkJsonParser          *self);
+gboolean                gtk_json_parser_start_array             (GtkJsonParser          *self);
+gboolean                gtk_json_parser_end                     (GtkJsonParser          *self);
+
+
+G_END_DECLS
+
+#endif /* __GTK_JSON_PARSER_H__ */
diff --git a/pango/json/gtkjsonprinter.c b/pango/json/gtkjsonprinter.c
new file mode 100644
index 00000000..f2f1e273
--- /dev/null
+++ b/pango/json/gtkjsonprinter.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#include "config.h"
+
+#include "gtkjsonprinterprivate.h"
+
+typedef struct _GtkJsonBlock GtkJsonBlock;
+
+typedef enum {
+  GTK_JSON_BLOCK_TOPLEVEL,
+  GTK_JSON_BLOCK_OBJECT,
+  GTK_JSON_BLOCK_ARRAY,
+} GtkJsonBlockType;
+
+struct _GtkJsonBlock
+{
+  GtkJsonBlockType type;
+  gsize n_elements; /* number of elements already written */
+};
+
+struct _GtkJsonPrinter
+{
+  GtkJsonPrinterFlags flags;
+  char *indentation;
+
+  GtkJsonPrinterWriteFunc write_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+
+  GtkJsonBlock *block; /* current block */
+  GtkJsonBlock *blocks; /* blocks array */
+  GtkJsonBlock *blocks_end; /* blocks array */
+  GtkJsonBlock blocks_preallocated[128]; /* preallocated */
+};
+
+static void
+gtk_json_printer_push_block (GtkJsonPrinter   *self,
+                             GtkJsonBlockType  type)
+{
+  self->block++;
+  if (self->block == self->blocks_end)
+    {
+      gsize old_size = self->blocks_end - self->blocks;
+      gsize new_size = old_size + 128;
+
+      if (self->blocks == self->blocks_preallocated)
+        {
+          self->blocks = g_new (GtkJsonBlock, new_size);
+          memcpy (self->blocks, self->blocks_preallocated, sizeof (GtkJsonBlock) * G_N_ELEMENTS 
(self->blocks_preallocated));
+        }
+      else
+        {
+          self->blocks = g_renew (GtkJsonBlock, self->blocks, new_size);
+        }
+      self->blocks_end = self->blocks + new_size;
+      self->block = self->blocks + old_size;
+    }
+
+  self->block->type = type;
+  self->block->n_elements = 0;
+}
+                     
+static void
+gtk_json_printer_pop_block (GtkJsonPrinter *self)
+{
+  g_assert (self->block > self->blocks);
+  self->block--;
+}
+
+GtkJsonPrinter *
+gtk_json_printer_new (GtkJsonPrinterWriteFunc write_func,
+                      gpointer                data,
+                      GDestroyNotify          destroy)
+{
+  GtkJsonPrinter *self;
+
+  g_return_val_if_fail (write_func, NULL);
+
+  self = g_slice_new0 (GtkJsonPrinter);
+
+  self->flags = 0;
+  self->indentation = g_strdup ("  ");
+
+  self->write_func = write_func;
+  self->user_data = data;
+  self->user_destroy = destroy;
+
+  self->blocks = self->blocks_preallocated;
+  self->blocks_end = self->blocks + G_N_ELEMENTS (self->blocks_preallocated);
+  self->block = self->blocks;
+  self->block->type = GTK_JSON_BLOCK_TOPLEVEL;
+
+  return self;
+}
+
+void
+gtk_json_printer_free (GtkJsonPrinter *self)
+{
+  g_return_if_fail (self != NULL);
+
+  g_free (self->indentation);
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  if (self->blocks != self->blocks_preallocated)
+    g_free (self->blocks);
+
+  g_slice_free (GtkJsonPrinter, self);
+}
+
+static gboolean
+gtk_json_printer_has_flag (GtkJsonPrinter      *self,
+                           GtkJsonPrinterFlags  flag)
+{
+  return (self->flags & flag) ? TRUE : FALSE;
+}
+                           
+gsize
+gtk_json_printer_get_depth (GtkJsonPrinter *self)
+{
+  return self->block - self->blocks;
+}
+
+gsize
+gtk_json_printer_get_n_elements (GtkJsonPrinter *self)
+{
+  return self->block->n_elements;
+}
+
+void
+gtk_json_printer_set_flags (GtkJsonPrinter      *self,
+                            GtkJsonPrinterFlags  flags)
+{
+  g_return_if_fail (self != NULL);
+
+  self->flags = flags;
+}
+
+GtkJsonPrinterFlags
+gtk_json_printer_get_flags (GtkJsonPrinter *self)
+{
+  g_return_val_if_fail (self != NULL, 0);
+
+  return self->flags;
+}
+
+void
+gtk_json_printer_set_indentation (GtkJsonPrinter *self,
+                                  gsize           amount)
+{
+  g_return_if_fail (self != NULL);
+
+  g_free (self->indentation);
+
+  self->indentation = g_malloc (amount + 1);
+  memset (self->indentation, ' ', amount);
+  self->indentation[amount] = 0;
+}
+
+gsize
+gtk_json_printer_get_indentation (GtkJsonPrinter *self)
+{
+  g_return_val_if_fail (self != NULL, 2);
+
+  return strlen (self->indentation);
+}
+
+static void
+gtk_json_printer_write (GtkJsonPrinter *self,
+                        const char     *s)
+{
+  self->write_func (self, s, self->user_data);
+}
+
+static char *
+gtk_json_printer_escape_string (GtkJsonPrinter *self,
+                                const char     *str)
+{
+  GString *string;
+
+  string = g_string_new (NULL);
+  string = g_string_append_c (string, '"');
+
+  for (; *str != '\0'; str = g_utf8_next_char (str))
+    {
+      switch (*str)
+        {
+          case '"':
+            g_string_append (string, "\\\"");
+            break;
+          case '\\':
+            g_string_append (string, "\\\\");
+            break;
+          case '\b':
+            g_string_append (string, "\\b");
+            break;
+          case '\f':
+            g_string_append (string, "\\f");
+            break;
+          case '\n':
+            g_string_append (string, "\\n");
+            break;
+          case '\r':
+            g_string_append (string, "\\r");
+            break;
+          case '\t':
+            g_string_append (string, "\\t");
+            break;
+          default:
+            if ((int) *str < 0x20)
+              {
+                if ((guint) *str < 0x20 || gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_ASCII))
+                  g_string_append_printf (string, "\\u%04x", g_utf8_get_char (str));
+                else
+                  g_string_append_unichar (string, g_utf8_get_char (str));
+              }
+            else
+              g_string_append_c (string, *str);
+        }
+    }
+
+  string = g_string_append_c (string, '"');
+  return g_string_free (string, FALSE);
+}
+
+static void
+gtk_json_printer_newline (GtkJsonPrinter *self)
+{
+  gsize depth;
+
+  if (!gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_PRETTY))
+    return;
+
+  gtk_json_printer_write (self, "\n");
+  for (depth = gtk_json_printer_get_depth (self); depth-->0;)
+    gtk_json_printer_write (self, self->indentation);
+}
+
+static void
+gtk_json_printer_begin_member (GtkJsonPrinter *self,
+                               const char     *name)
+{
+  if (gtk_json_printer_get_n_elements (self) > 0)
+    gtk_json_printer_write (self, ",");
+  if (self->block->type != GTK_JSON_BLOCK_TOPLEVEL || gtk_json_printer_get_n_elements (self) > 0)
+    gtk_json_printer_newline (self);
+
+  self->block->n_elements++;
+
+  if (name)
+    {
+      char *escaped = gtk_json_printer_escape_string (self, name);
+      gtk_json_printer_write (self, escaped);
+      g_free (escaped);
+      if (gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_PRETTY))
+        gtk_json_printer_write (self, " : ");
+      else
+        gtk_json_printer_write (self, ":");
+    }
+}
+
+void
+gtk_json_printer_add_boolean (GtkJsonPrinter *self,
+                              const char     *name,
+                              gboolean        value)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+  gtk_json_printer_begin_member (self, name);
+  gtk_json_printer_write (self, value ? "true" : "false");
+}
+
+void
+gtk_json_printer_add_number (GtkJsonPrinter *self,
+                             const char     *name,
+                             double          value)
+{
+  char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+  g_return_if_fail (self != NULL);
+  g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+  gtk_json_printer_begin_member (self, name);
+  g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, value);
+  gtk_json_printer_write (self, buf);
+}
+
+void
+gtk_json_printer_add_integer (GtkJsonPrinter *self,
+                              const char     *name,
+                              int             value)
+{
+  char buf[128];
+
+  g_return_if_fail (self != NULL);
+  g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+  gtk_json_printer_begin_member (self, name);
+  g_snprintf (buf, sizeof (buf), "%d", value);
+  gtk_json_printer_write (self, buf);
+}
+
+void
+gtk_json_printer_add_string (GtkJsonPrinter *self,
+                             const char     *name,
+                             const char     *s)
+{
+  char *escaped;
+
+  g_return_if_fail (self != NULL);
+  g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+  g_return_if_fail (s != NULL);
+
+  gtk_json_printer_begin_member (self, name);
+  escaped = gtk_json_printer_escape_string (self, s);
+  gtk_json_printer_write (self, escaped);
+  g_free (escaped);
+}
+
+void
+gtk_json_printer_add_null (GtkJsonPrinter *self,
+                           const char     *name)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+  gtk_json_printer_begin_member (self, name);
+  gtk_json_printer_write (self, "null");
+}
+
+void
+gtk_json_printer_start_object (GtkJsonPrinter *self,
+                               const char     *name)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+  gtk_json_printer_begin_member (self, name);
+  gtk_json_printer_write (self, "{");
+  gtk_json_printer_push_block (self, GTK_JSON_BLOCK_OBJECT);
+}
+
+void
+gtk_json_printer_start_array (GtkJsonPrinter *self,
+                              const char     *name)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+  gtk_json_printer_begin_member (self, name);
+  gtk_json_printer_write (self, "[");
+  gtk_json_printer_push_block (self, GTK_JSON_BLOCK_ARRAY);
+}
+
+void
+gtk_json_printer_end (GtkJsonPrinter *self)
+{
+  const char *bracket;
+  gboolean empty;
+
+  g_return_if_fail (self != NULL);
+
+  switch (self->block->type)
+    {
+    case GTK_JSON_BLOCK_OBJECT:
+      bracket = "}";
+      break;
+    case GTK_JSON_BLOCK_ARRAY:
+      bracket = "]";
+      break;
+    case GTK_JSON_BLOCK_TOPLEVEL:
+    default:
+      g_return_if_reached ();
+    }
+
+  empty = gtk_json_printer_get_n_elements (self) == 0;
+  gtk_json_printer_pop_block (self);
+
+  if (!empty)
+    {
+      gtk_json_printer_newline (self);
+    }
+  gtk_json_printer_write (self, bracket);
+}
+
diff --git a/pango/json/gtkjsonprinterprivate.h b/pango/json/gtkjsonprinterprivate.h
new file mode 100644
index 00000000..e25a1b1d
--- /dev/null
+++ b/pango/json/gtkjsonprinterprivate.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_JSON_PRINTER_H__
+#define __GTK_JSON_PRINTER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkJsonPrinter GtkJsonPrinter;
+
+typedef enum {
+  GTK_JSON_PRINTER_PRETTY = (1 << 0),
+  GTK_JSON_PRINTER_ASCII  = (1 << 1),
+} GtkJsonPrinterFlags;
+
+typedef void            (* GtkJsonPrinterWriteFunc)             (GtkJsonPrinter         *printer,
+                                                                 const char             *s,
+                                                                 gpointer                user_data);
+             
+
+GtkJsonPrinter *        gtk_json_printer_new                    (GtkJsonPrinterWriteFunc write_func,
+                                                                 gpointer                data,
+                                                                 GDestroyNotify          destroy);
+void                    gtk_json_printer_free                   (GtkJsonPrinter         *self);
+
+void                    gtk_json_printer_set_flags              (GtkJsonPrinter         *self,
+                                                                 GtkJsonPrinterFlags     flags);
+GtkJsonPrinterFlags     gtk_json_printer_get_flags              (GtkJsonPrinter         *self);
+void                    gtk_json_printer_set_indentation        (GtkJsonPrinter         *self,
+                                                                 gsize                   amount);
+gsize                   gtk_json_printer_get_indentation        (GtkJsonPrinter         *self);
+
+gsize                   gtk_json_printer_get_depth              (GtkJsonPrinter         *self);
+gsize                   gtk_json_printer_get_n_elements         (GtkJsonPrinter         *self);
+
+void                    gtk_json_printer_add_boolean            (GtkJsonPrinter         *self,
+                                                                 const char             *name,
+                                                                 gboolean                value);
+void                    gtk_json_printer_add_number             (GtkJsonPrinter         *self,
+                                                                 const char             *name,
+                                                                 double                  value);
+void                    gtk_json_printer_add_integer            (GtkJsonPrinter         *self,
+                                                                 const char             *name,
+                                                                 int                     value);
+void                    gtk_json_printer_add_string             (GtkJsonPrinter         *self,
+                                                                 const char             *name,
+                                                                 const char             *s);
+void                    gtk_json_printer_add_null               (GtkJsonPrinter         *self,
+                                                                 const char             *name);
+
+void                    gtk_json_printer_start_object           (GtkJsonPrinter         *self,
+                                                                 const char             *name);
+void                    gtk_json_printer_start_array            (GtkJsonPrinter         *self,
+                                                                 const char             *name);
+void                    gtk_json_printer_end                    (GtkJsonPrinter         *self);
+
+
+G_END_DECLS
+
+#endif /* __GTK_JSON_PRINTER_H__ */
diff --git a/pango/meson.build b/pango/meson.build
index 7c7bb280..4e6f746e 100644
--- a/pango/meson.build
+++ b/pango/meson.build
@@ -28,6 +28,8 @@ pango_sources = [
   'reorder-items.c',
   'shape.c',
   'serializer.c',
+  'json/gtkjsonparser.c',
+  'json/gtkjsonprinter.c',
 ]
 
 pango_headers = [
diff --git a/pango/serializer.c b/pango/serializer.c
index fe0fc25c..c52820e9 100644
--- a/pango/serializer.c
+++ b/pango/serializer.c
@@ -28,7 +28,8 @@
 #include <pango/pango-font-private.h>
 
 #include <hb-ot.h>
-#include <json-glib/json-glib.h>
+#include "pango/json/gtkjsonparserprivate.h"
+#include "pango/json/gtkjsonprinterprivate.h"
 
 /* {{{ Error handling */
 
@@ -58,10 +59,11 @@ get_enum_type (PangoAttrType attr_type)
 }
 
 static void
-add_enum_value (JsonBuilder   *builder,
-                GType          type,
-                int            value,
-                gboolean       allow_extra)
+add_enum_value (GtkJsonPrinter *printer,
+                const char     *member,
+                GType           type,
+                int             value,
+                gboolean        allow_extra)
 {
   GEnumClass *enum_class;
   GEnumValue *enum_value;
@@ -70,51 +72,38 @@ add_enum_value (JsonBuilder   *builder,
   enum_value = g_enum_get_value (enum_class, value);
 
   if (enum_value)
-    json_builder_add_string_value (builder, enum_value->value_nick);
+    gtk_json_printer_add_string (printer, member, enum_value->value_nick);
   else if (allow_extra)
-    {
-      char buf[128];
-      g_snprintf (buf, 128, "%d", value);
-      json_builder_add_string_value (builder, buf);
-    }
+    gtk_json_printer_add_integer (printer, member, value);
   else
-    json_builder_add_string_value (builder, "ERROR");
-
+    gtk_json_printer_add_string (printer, member, "ERROR");
 }
 
 static void
-add_attribute (JsonBuilder    *builder,
+add_attribute (GtkJsonPrinter *printer,
                PangoAttribute *attr)
 {
   char *str;
 
-  json_builder_begin_object (builder);
+  gtk_json_printer_start_object (printer, NULL);
 
   if (attr->start_index != PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING)
-    {
-      json_builder_set_member_name (builder, "start");
-      json_builder_add_int_value (builder, (int)attr->start_index);
-    }
+    gtk_json_printer_add_integer (printer, "start", (int)attr->start_index);
   if (attr->end_index != PANGO_ATTR_INDEX_TO_TEXT_END)
-    {
-      json_builder_set_member_name (builder, "end");
-      json_builder_add_int_value (builder, (int)attr->end_index);
-    }
-  json_builder_set_member_name (builder, "type");
-  add_enum_value (builder, PANGO_TYPE_ATTR_TYPE, attr->klass->type, FALSE);
+    gtk_json_printer_add_integer (printer, "end", (int)attr->start_index);
+  add_enum_value (printer, "type", PANGO_TYPE_ATTR_TYPE, attr->klass->type, FALSE);
 
-  json_builder_set_member_name (builder, "value");
   switch (attr->klass->type)
     {
     default:
     case PANGO_ATTR_INVALID:
       g_assert_not_reached ();
     case PANGO_ATTR_LANGUAGE:
-      json_builder_add_string_value (builder, pango_language_to_string (((PangoAttrLanguage*)attr)->value));
+      gtk_json_printer_add_string (printer, "value", pango_language_to_string 
(((PangoAttrLanguage*)attr)->value));
       break;
     case PANGO_ATTR_FAMILY:
     case PANGO_ATTR_FONT_FEATURES:
-      json_builder_add_string_value (builder, ((PangoAttrString*)attr)->value);
+      gtk_json_printer_add_string (printer, "value", ((PangoAttrString*)attr)->value);
       break;
     case PANGO_ATTR_STYLE:
     case PANGO_ATTR_VARIANT:
@@ -125,11 +114,11 @@ add_attribute (JsonBuilder    *builder,
     case PANGO_ATTR_GRAVITY_HINT:
     case PANGO_ATTR_TEXT_TRANSFORM:
     case PANGO_ATTR_FONT_SCALE:
-      add_enum_value (builder, get_enum_type (attr->klass->type), ((PangoAttrInt*)attr)->value, FALSE);
+      add_enum_value (printer, "value", get_enum_type (attr->klass->type), ((PangoAttrInt*)attr)->value, 
FALSE);
       break;
     case PANGO_ATTR_WEIGHT:
     case PANGO_ATTR_BASELINE_SHIFT:
-      add_enum_value (builder, get_enum_type (attr->klass->type), ((PangoAttrInt*)attr)->value, TRUE);
+      add_enum_value (printer, "value", get_enum_type (attr->klass->type), ((PangoAttrInt*)attr)->value, 
TRUE);
       break;
     case PANGO_ATTR_SIZE:
     case PANGO_ATTR_RISE:
@@ -141,11 +130,11 @@ add_attribute (JsonBuilder    *builder,
     case PANGO_ATTR_WORD:
     case PANGO_ATTR_SENTENCE:
     case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
-      json_builder_add_int_value (builder, ((PangoAttrInt*)attr)->value);
+      gtk_json_printer_add_integer (printer, "value", ((PangoAttrInt*)attr)->value);
       break;
     case PANGO_ATTR_FONT_DESC:
       str = pango_font_description_to_string (((PangoAttrFontDesc*)attr)->desc);
-      json_builder_add_string_value (builder, str);
+      gtk_json_printer_add_string (printer, "value", str);
       g_free (str);
       break;
     case PANGO_ATTR_FOREGROUND:
@@ -154,29 +143,29 @@ add_attribute (JsonBuilder    *builder,
     case PANGO_ATTR_OVERLINE_COLOR:
     case PANGO_ATTR_STRIKETHROUGH_COLOR:
       str = pango_color_to_string (&((PangoAttrColor*)attr)->color);
-      json_builder_add_string_value (builder, str);
+      gtk_json_printer_add_string (printer, "value", str);
       g_free (str);
       break;
     case PANGO_ATTR_STRIKETHROUGH:
     case PANGO_ATTR_FALLBACK:
     case PANGO_ATTR_ALLOW_BREAKS:
     case PANGO_ATTR_INSERT_HYPHENS:
-      json_builder_add_boolean_value (builder, ((PangoAttrInt*)attr)->value != 0);
+      gtk_json_printer_add_boolean (printer, "value", ((PangoAttrInt*)attr)->value != 0);
       break;
     case PANGO_ATTR_SHAPE:
-      json_builder_add_string_value (builder, "shape");
+      gtk_json_printer_add_string (printer, "value", "shape");
       break;
     case PANGO_ATTR_SCALE:
     case PANGO_ATTR_LINE_HEIGHT:
-      json_builder_add_double_value (builder, ((PangoAttrFloat*)attr)->value);
+      gtk_json_printer_add_number (printer, "value", ((PangoAttrFloat*)attr)->value);
     }
 
-  json_builder_end_object (builder);
+  gtk_json_printer_end (printer);
 }
 
 static void
-add_attr_list (JsonBuilder   *builder,
-               PangoAttrList *attrs)
+add_attr_list (GtkJsonPrinter *printer,
+               PangoAttrList  *attrs)
 {
   GSList *attributes, *l;
 
@@ -188,62 +177,54 @@ add_attr_list (JsonBuilder   *builder,
   if (!attributes)
     return;
 
-  json_builder_set_member_name (builder, "attributes");
-  json_builder_begin_array (builder);
+  gtk_json_printer_start_array (printer, "attributes");
 
   for (l = attributes; l; l = l->next)
     {
       PangoAttribute *attr = l->data;
-      add_attribute (builder, attr);
+      add_attribute (printer, attr);
     }
   g_slist_free_full (attributes, (GDestroyNotify) pango_attribute_destroy);
 
-  json_builder_end_array (builder);
+  gtk_json_printer_end (printer);
 }
 
 static void
-add_tab_array (JsonBuilder   *builder,
-               PangoTabArray *tabs)
+add_tab_array (GtkJsonPrinter *printer,
+               PangoTabArray  *tabs)
 {
   if (!tabs || pango_tab_array_get_size (tabs) == 0)
     return;
 
-  json_builder_set_member_name (builder, "tabs");
-
-  json_builder_begin_object (builder);
+  gtk_json_printer_start_object (printer, "tabs");
 
-  json_builder_set_member_name (builder, "positions-in-pixels");
-  json_builder_add_boolean_value (builder, pango_tab_array_get_positions_in_pixels (tabs));
-  json_builder_set_member_name (builder, "positions");
-  json_builder_begin_array (builder);
+  gtk_json_printer_add_boolean (printer, "position-in-pixels", pango_tab_array_get_positions_in_pixels 
(tabs));
+  gtk_json_printer_start_array (printer, "positions");
   for (int i = 0; i < pango_tab_array_get_size (tabs); i++)
     {
       PangoTabAlign align;
       int pos;
       pango_tab_array_get_tab (tabs, i, &align, &pos);
-      json_builder_begin_object (builder);
-      json_builder_set_member_name (builder, "position");
-      json_builder_add_int_value (builder, pos);
-      json_builder_set_member_name (builder, "alignment");
-      add_enum_value (builder, PANGO_TYPE_TAB_ALIGN, align, FALSE);
-      json_builder_set_member_name (builder, "decimal-point");
-      json_builder_add_int_value (builder, pango_tab_array_get_decimal_point (tabs, i));
-      json_builder_end_object (builder);
+      gtk_json_printer_start_object (printer, NULL);
+      gtk_json_printer_add_integer (printer, "position", pos);
+      add_enum_value (printer, "alignment", PANGO_TYPE_TAB_ALIGN, align, FALSE);
+      gtk_json_printer_add_integer (printer, "decimal-point", pango_tab_array_get_decimal_point (tabs, i));
+      gtk_json_printer_end (printer);
     }
-  json_builder_end_array (builder);
+  gtk_json_printer_end (printer);
 
-  json_builder_end_object (builder);
+  gtk_json_printer_end (printer);
 }
 
 static void
-add_context (JsonBuilder  *builder,
-             PangoContext *context)
+add_context (GtkJsonPrinter *printer,
+             PangoContext   *context)
 {
   char *str;
   const PangoMatrix *matrix;
   PangoMatrix identity = PANGO_MATRIX_INIT;
 
-  json_builder_begin_object (builder);
+  gtk_json_printer_start_object (printer, "context");
 
   /* Note: since we don't create the context when deserializing,
    * we don't strip out default values here to ensure that the
@@ -251,143 +232,86 @@ add_context (JsonBuilder  *builder,
    */
 
   str = pango_font_description_to_string (context->font_desc);
-  json_builder_set_member_name (builder, "font");
-  json_builder_add_string_value (builder, str);
+  gtk_json_printer_add_string (printer, "font", str);
   g_free (str);
 
   if (context->set_language)
-    {
-      json_builder_set_member_name (builder, "language");
-      json_builder_add_string_value (builder, pango_language_to_string (context->set_language));
-    }
+    gtk_json_printer_add_string (printer, "language", pango_language_to_string (context->set_language));
 
-  json_builder_set_member_name (builder, "base-gravity");
-  add_enum_value (builder, PANGO_TYPE_GRAVITY, context->base_gravity, FALSE);
+  add_enum_value (printer, "base-gravity", PANGO_TYPE_GRAVITY, context->base_gravity, FALSE);
+  add_enum_value (printer, "gravity-hint", PANGO_TYPE_GRAVITY_HINT, context->gravity_hint, FALSE);
+  add_enum_value (printer, "base-dir", PANGO_TYPE_DIRECTION, context->base_dir, FALSE);
+  gtk_json_printer_add_boolean (printer, "round-glyph-positions", context->round_glyph_positions);
 
-  json_builder_set_member_name (builder, "gravity-hint");
-  add_enum_value (builder, PANGO_TYPE_GRAVITY_HINT, context->gravity_hint, FALSE);
-
-  json_builder_set_member_name (builder, "base-dir");
-  add_enum_value (builder, PANGO_TYPE_DIRECTION, context->base_dir, FALSE);
-
-  json_builder_set_member_name (builder, "round-glyph-positions");
-  json_builder_add_boolean_value (builder, context->round_glyph_positions);
-
-  json_builder_set_member_name (builder, "transform");
   matrix = pango_context_get_matrix (context);
   if (!matrix)
     matrix = &identity;
 
-  json_builder_begin_array (builder);
-  json_builder_add_double_value (builder, matrix->xx);
-  json_builder_add_double_value (builder, matrix->xy);
-  json_builder_add_double_value (builder, matrix->yx);
-  json_builder_add_double_value (builder, matrix->yy);
-  json_builder_add_double_value (builder, matrix->x0);
-  json_builder_add_double_value (builder, matrix->y0);
-  json_builder_end_array (builder);
+  gtk_json_printer_start_array (printer, "transform");
+  gtk_json_printer_add_number (printer, NULL, matrix->xx);
+  gtk_json_printer_add_number (printer, NULL, matrix->xy);
+  gtk_json_printer_add_number (printer, NULL, matrix->yx);
+  gtk_json_printer_add_number (printer, NULL, matrix->yy);
+  gtk_json_printer_add_number (printer, NULL, matrix->x0);
+  gtk_json_printer_add_number (printer, NULL, matrix->y0);
+  gtk_json_printer_end (printer);
 
-  json_builder_end_object (builder);
+  gtk_json_printer_end (printer);
 }
 
 static void
-add_log_attrs (JsonBuilder *builder,
-               PangoLayout *layout)
+add_log_attrs (GtkJsonPrinter *printer,
+               PangoLayout    *layout)
 {
   const PangoLogAttr *log_attrs;
   int n_attrs;
 
-  json_builder_set_member_name (builder, "log-attrs");
-  json_builder_begin_array (builder);
+  gtk_json_printer_start_array (printer, "log-attrs");
 
   log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);
   for (int i = 0; i < n_attrs; i++)
     {
-      json_builder_begin_object (builder);
+      gtk_json_printer_start_object (printer, NULL);
       if (log_attrs[i].is_line_break)
-        {
-          json_builder_set_member_name (builder, "line-break");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "line-break", TRUE);
       if (log_attrs[i].is_mandatory_break)
-        {
-          json_builder_set_member_name (builder, "mandatory-break");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "mandatory-break", TRUE);
       if (log_attrs[i].is_char_break)
-        {
-          json_builder_set_member_name (builder, "char-break");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "char-break", TRUE);
       if (log_attrs[i].is_white)
-        {
-          json_builder_set_member_name (builder, "white");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "white", TRUE);
       if (log_attrs[i].is_cursor_position)
-        {
-          json_builder_set_member_name (builder, "cursor-position");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "cursor-position", TRUE);
       if (log_attrs[i].is_word_start)
-        {
-          json_builder_set_member_name (builder, "word-start");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "word-start", TRUE);
       if (log_attrs[i].is_word_end)
-        {
-          json_builder_set_member_name (builder, "word-end");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "word-end", TRUE);
       if (log_attrs[i].is_sentence_boundary)
-        {
-          json_builder_set_member_name (builder, "sentence-boundary");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "sentence-boundary", TRUE);
       if (log_attrs[i].is_sentence_start)
-        {
-          json_builder_set_member_name (builder, "sentence-start");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "sentence-start", TRUE);
       if (log_attrs[i].is_sentence_end)
-        {
-          json_builder_set_member_name (builder, "sentence-end");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "sentence-end", TRUE);
       if (log_attrs[i].backspace_deletes_character)
-        {
-          json_builder_set_member_name (builder, "backspace-deletes-character");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "backspace-deletes-character", TRUE);
       if (log_attrs[i].is_expandable_space)
-        {
-          json_builder_set_member_name (builder, "expandable-space");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "expandable-space", TRUE);
       if (log_attrs[i].is_word_boundary)
-        {
-          json_builder_set_member_name (builder, "word-boundary");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "word-boundary", TRUE);
       if (log_attrs[i].break_inserts_hyphen)
-        {
-          json_builder_set_member_name (builder, "break-inserts-hyphen");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "break-inserts-hyphen", TRUE);
       if (log_attrs[i].break_removes_preceding)
-        {
-          json_builder_set_member_name (builder, "break-removes_preceding");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
-      json_builder_end_object (builder);
+        gtk_json_printer_add_boolean (printer, "break-removes-preceding", TRUE);
+      gtk_json_printer_end (printer);
     }
 
-  json_builder_end_array (builder);
+  gtk_json_printer_end (printer);
 }
 
 static void
-add_font (JsonBuilder *builder,
-          PangoFont   *font)
+add_font (GtkJsonPrinter *printer,
+          const char     *member,
+          PangoFont      *font)
 {
   PangoFontDescription *desc;
   char *str;
@@ -400,13 +324,11 @@ add_font (JsonBuilder *builder,
   hb_feature_t features[32];
   PangoMatrix matrix;
 
-  json_builder_begin_object (builder);
-
-  json_builder_set_member_name (builder, "description");
+  gtk_json_printer_start_object (printer, member);
 
   desc = pango_font_describe (font);
   str = pango_font_description_to_string (desc);
-  json_builder_add_string_value (builder, str);
+  gtk_json_printer_add_string (printer, "description", str);
   g_free (str);
   pango_font_description_free (desc);
 
@@ -417,8 +339,7 @@ add_font (JsonBuilder *builder,
   data = hb_blob_get_data (blob, &length);
   str = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (const guchar *)data, length);
 
-  json_builder_set_member_name (builder, "checksum");
-  json_builder_add_string_value (builder, str);
+  gtk_json_printer_add_string (printer, "checksum", str);
 
   g_free (str);
   hb_blob_destroy (blob);
@@ -435,55 +356,50 @@ add_font (JsonBuilder *builder,
       axes = g_alloca (count * sizeof (hb_ot_var_axis_info_t));
       hb_ot_var_get_axis_infos (face, 0, &count, axes);
 
-      json_builder_set_member_name (builder, "variations");
-      json_builder_begin_object (builder);
+      gtk_json_printer_start_object (printer, "variations");
 
       for (int i = 0; i < length; i++)
         {
           char buf[5] = { 0, };
 
           hb_tag_to_string (axes[i].tag, buf);
-          json_builder_set_member_name (builder, buf);
-          json_builder_add_int_value (builder, coords[i]);
+          gtk_json_printer_add_integer (printer, buf, coords[i]);
         }
 
-      json_builder_end_object (builder);
+      gtk_json_printer_end (printer);
     }
 
   length = 0;
   pango_font_get_features (font, features, G_N_ELEMENTS (features), &length);
   if (length > 0)
     {
-      json_builder_set_member_name (builder, "features");
-      json_builder_begin_object (builder);
+      gtk_json_printer_start_object (printer, "features");
 
       for (int i = 0; i < length; i++)
         {
           char buf[5] = { 0, };
 
           hb_tag_to_string (features[i].tag, buf);
-          json_builder_set_member_name (builder, buf);
-          json_builder_add_int_value (builder, features[i].value);
+          gtk_json_printer_add_integer (printer, buf, features[i].value);
         }
 
-      json_builder_end_object (builder);
+      gtk_json_printer_end (printer);
     }
 
   pango_font_get_matrix (font, &matrix);
   if (memcmp (&matrix, &(PangoMatrix)PANGO_MATRIX_INIT, sizeof (PangoMatrix)) != 0)
     {
-      json_builder_set_member_name (builder, "matrix");
-      json_builder_begin_array (builder);
-      json_builder_add_double_value (builder, matrix.xx);
-      json_builder_add_double_value (builder, matrix.xy);
-      json_builder_add_double_value (builder, matrix.yx);
-      json_builder_add_double_value (builder, matrix.yy);
-      json_builder_add_double_value (builder, matrix.x0);
-      json_builder_add_double_value (builder, matrix.y0);
-      json_builder_end_array (builder);
+      gtk_json_printer_start_array (printer, "matrix");
+      gtk_json_printer_add_number (printer, NULL, matrix.xx);
+      gtk_json_printer_add_number (printer, NULL, matrix.xy);
+      gtk_json_printer_add_number (printer, NULL, matrix.yx);
+      gtk_json_printer_add_number (printer, NULL, matrix.yy);
+      gtk_json_printer_add_number (printer, NULL, matrix.x0);
+      gtk_json_printer_add_number (printer, NULL, matrix.y0);
+      gtk_json_printer_end (printer);
     }
 
-  json_builder_end_object (builder);
+  gtk_json_printer_end (printer);
 }
 
 #define ANALYSIS_FLAGS (PANGO_ANALYSIS_FLAG_CENTERED_BASELINE | \
@@ -491,359 +407,259 @@ add_font (JsonBuilder *builder,
                         PANGO_ANALYSIS_FLAG_NEED_HYPHEN)
 
 static void
-add_run (JsonBuilder    *builder,
+add_run (GtkJsonPrinter *printer,
          PangoLayout    *layout,
          PangoLayoutRun *run)
 {
-  json_builder_begin_object (builder);
   char *str;
 
-  json_builder_set_member_name (builder, "offset");
-  json_builder_add_int_value (builder, run->item->offset);
+  gtk_json_printer_start_object (printer, NULL);
 
-  json_builder_set_member_name (builder, "length");
-  json_builder_add_int_value (builder, run->item->length);
+  gtk_json_printer_add_integer (printer, "offset", run->item->offset);
+  gtk_json_printer_add_integer (printer, "length", run->item->length);
 
   str = g_strndup (layout->text + run->item->offset, run->item->length);
-  json_builder_set_member_name (builder, "text");
-  json_builder_add_string_value (builder, str);
+  gtk_json_printer_add_string (printer, "text ", str);
   g_free (str);
 
-  json_builder_set_member_name (builder, "bidi-level");
-  json_builder_add_int_value (builder, run->item->analysis.level);
-
-  json_builder_set_member_name (builder, "gravity");
-  add_enum_value (builder, PANGO_TYPE_GRAVITY, run->item->analysis.gravity, FALSE);
+  gtk_json_printer_add_integer (printer, "bidi-level", run->item->analysis.level);
+  add_enum_value (printer, "gravity", PANGO_TYPE_GRAVITY, run->item->analysis.gravity, FALSE);
+  gtk_json_printer_add_string (printer, "language", pango_language_to_string (run->item->analysis.language));
+  add_enum_value (printer, "script", PANGO_TYPE_SCRIPT, run->item->analysis.script, FALSE);
 
-  json_builder_set_member_name (builder, "language");
-  json_builder_add_string_value (builder, pango_language_to_string (run->item->analysis.language));
+  add_font (printer, "font", run->item->analysis.font);
 
-  json_builder_set_member_name (builder, "script");
-  add_enum_value (builder, PANGO_TYPE_SCRIPT, run->item->analysis.script, FALSE);
-
-  json_builder_set_member_name (builder, "font");
-  add_font (builder, run->item->analysis.font);
-
-  json_builder_set_member_name (builder, "flags");
-  json_builder_add_int_value (builder, run->item->analysis.flags & ANALYSIS_FLAGS);
+  gtk_json_printer_add_integer (printer, "flags", run->item->analysis.flags & ANALYSIS_FLAGS);
 
   if (run->item->analysis.extra_attrs)
     {
       GSList *l;
 
-      json_builder_set_member_name (builder, "extra-attributes");
-
-      json_builder_begin_array (builder);
+      gtk_json_printer_start_array (printer, "extra-attributes");
       for (l = run->item->analysis.extra_attrs; l; l = l->next)
         {
           PangoAttribute *attr = l->data;
-          add_attribute (builder, attr);
+          add_attribute (printer, attr);
         }
-      json_builder_end_array (builder);
+      gtk_json_printer_end (printer);
     }
 
-  json_builder_set_member_name (builder, "y-offset");
-  json_builder_add_int_value (builder, run->y_offset);
-
-  json_builder_set_member_name (builder, "start-x-offset");
-  json_builder_add_int_value (builder, run->start_x_offset);
-
-  json_builder_set_member_name (builder, "end-x-offset");
-  json_builder_add_int_value (builder, run->end_x_offset);
+  gtk_json_printer_add_integer (printer, "y-offset", run->y_offset);
+  gtk_json_printer_add_integer (printer, "start-x-offset", run->start_x_offset);
+  gtk_json_printer_add_integer (printer, "end-x-offset", run->end_x_offset);
 
-  json_builder_set_member_name (builder, "glyphs");
-  json_builder_begin_array (builder);
+  gtk_json_printer_start_array (printer, "glyphs");
   for (int i = 0; i < run->glyphs->num_glyphs; i++)
     {
-      json_builder_begin_object (builder);
+      gtk_json_printer_start_object (printer, NULL);
 
-      json_builder_set_member_name (builder, "glyph");
-      json_builder_add_int_value (builder, run->glyphs->glyphs[i].glyph);
-
-      json_builder_set_member_name (builder, "width");
-      json_builder_add_int_value (builder, run->glyphs->glyphs[i].geometry.width);
+      gtk_json_printer_add_integer (printer, "glyph", run->glyphs->glyphs[i].glyph);
+      gtk_json_printer_add_integer (printer, "width", run->glyphs->glyphs[i].geometry.width);
 
       if (run->glyphs->glyphs[i].geometry.x_offset != 0)
-        {
-          json_builder_set_member_name (builder, "x-offset");
-          json_builder_add_int_value (builder, run->glyphs->glyphs[i].geometry.x_offset);
-        }
+        gtk_json_printer_add_integer (printer, "x-offset", run->glyphs->glyphs[i].geometry.x_offset);
 
       if (run->glyphs->glyphs[i].geometry.y_offset != 0)
-        {
-          json_builder_set_member_name (builder, "y-offset");
-          json_builder_add_int_value (builder, run->glyphs->glyphs[i].geometry.y_offset);
-        }
+        gtk_json_printer_add_integer (printer, "y-offset", run->glyphs->glyphs[i].geometry.y_offset);
 
       if (run->glyphs->glyphs[i].attr.is_cluster_start)
-        {
-          json_builder_set_member_name (builder, "is-cluster-start");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "is-cluster-start", TRUE);
 
       if (run->glyphs->glyphs[i].attr.is_color)
-        {
-          json_builder_set_member_name (builder, "is-color");
-          json_builder_add_boolean_value (builder, TRUE);
-        }
+        gtk_json_printer_add_boolean (printer, "is-color", TRUE);
 
-      json_builder_set_member_name (builder, "log-cluster");
-      json_builder_add_int_value (builder, run->glyphs->log_clusters[i]);
+      gtk_json_printer_add_integer (printer, "log-cluster", run->glyphs->log_clusters[i]);
 
-      json_builder_end_object (builder);
+      gtk_json_printer_end (printer);
     }
 
-  json_builder_end_array (builder);
+  gtk_json_printer_end (printer);
 
-  json_builder_end_object (builder);
+  gtk_json_printer_end (printer);
 }
 
 #undef ANALYSIS_FLAGS
 
 static void
-add_line (JsonBuilder     *builder,
+add_line (GtkJsonPrinter  *printer,
           PangoLayoutLine *line)
 {
-  json_builder_begin_object (builder);
-
-  json_builder_set_member_name (builder, "start-index");
-  json_builder_add_int_value (builder, line->start_index);
+  gtk_json_printer_start_object (printer, NULL);
 
-  json_builder_set_member_name (builder, "length");
-  json_builder_add_int_value (builder, line->length);
+  gtk_json_printer_add_integer (printer, "start-index", line->start_index);
+  gtk_json_printer_add_integer (printer, "length", line->length);
+  gtk_json_printer_add_boolean (printer, "paragraph-start", line->is_paragraph_start);
+  add_enum_value (printer, "direction", PANGO_TYPE_DIRECTION, line->resolved_dir, FALSE);
 
-  json_builder_set_member_name (builder, "paragraph-start");
-  json_builder_add_boolean_value (builder, line->is_paragraph_start);
-
-  json_builder_set_member_name (builder, "direction");
-  add_enum_value (builder, PANGO_TYPE_DIRECTION, line->resolved_dir, FALSE);
-
-  json_builder_set_member_name (builder, "runs");
-  json_builder_begin_array (builder);
+  gtk_json_printer_start_array (printer, "runs");
   for (GSList *l = line->runs; l; l = l->next)
     {
       PangoLayoutRun *run = l->data;
-      add_run (builder, line->layout, run);
+      add_run (printer, line->layout, run);
     }
-  json_builder_end_array (builder);
+  gtk_json_printer_end (printer);
 
-  json_builder_end_object (builder);
+  gtk_json_printer_end (printer);
 }
 
 static void
-add_output (JsonBuilder *builder,
-            PangoLayout *layout)
+add_output (GtkJsonPrinter *printer,
+            PangoLayout    *layout)
 {
   int width, height;
 
-  json_builder_begin_object (builder);
-
-  json_builder_set_member_name (builder, "is-wrapped");
-  json_builder_add_boolean_value (builder, pango_layout_is_wrapped (layout));
+  gtk_json_printer_start_object (printer, "output");
 
-  json_builder_set_member_name (builder, "is-ellipsized");
-  json_builder_add_boolean_value (builder, pango_layout_is_ellipsized (layout));
-
-  json_builder_set_member_name (builder, "unknown-glyphs");
-  json_builder_add_int_value (builder, pango_layout_get_unknown_glyphs_count (layout));
+  gtk_json_printer_add_boolean (printer, "is-wrapped", pango_layout_is_wrapped (layout));
+  gtk_json_printer_add_boolean (printer, "is-ellipsized", pango_layout_is_ellipsized (layout));
+  gtk_json_printer_add_integer (printer, "unknown-glyphs", pango_layout_get_unknown_glyphs_count (layout));
 
   pango_layout_get_size (layout, &width, &height);
-  json_builder_set_member_name (builder, "width");
-  json_builder_add_int_value (builder, width);
-  json_builder_set_member_name (builder, "height");
-  json_builder_add_int_value (builder, height);
-
-  add_log_attrs (builder, layout);
-  json_builder_set_member_name (builder, "lines");
-  json_builder_begin_array (builder);
+  gtk_json_printer_add_integer (printer, "width", width);
+  gtk_json_printer_add_integer (printer, "height", width);
+
+  add_log_attrs (printer, layout);
+  gtk_json_printer_start_array (printer, "lines");
   for (GSList *l = layout->lines; l; l = l->next)
     {
       PangoLayoutLine *line = l->data;
-      add_line (builder, line);
+      add_line (printer, line);
     }
-  json_builder_end_array (builder);
+  gtk_json_printer_end (printer);
 
-  json_builder_end_object (builder);
+  gtk_json_printer_end (printer);
 }
 
-static JsonNode *
-layout_to_json (PangoLayout               *layout,
+static void
+layout_to_json (GtkJsonPrinter            *printer,
+                PangoLayout               *layout,
                 PangoLayoutSerializeFlags  flags)
 {
-  JsonBuilder *builder;
-  JsonNode *root;
   const char *str;
 
-  builder = json_builder_new_immutable ();
-
-  json_builder_begin_object (builder);
+  gtk_json_printer_start_object (printer, NULL);
 
   if (flags & PANGO_LAYOUT_SERIALIZE_CONTEXT)
-    {
-      json_builder_set_member_name (builder, "context");
-      add_context (builder, layout->context);
-    }
+    add_context (printer, layout->context);
 
   str = (const char *) g_object_get_data (G_OBJECT (layout), "comment");
   if (str)
     {
-      json_builder_set_member_name (builder, "comment");
       if (strstr (str, "\n") != NULL)
         {
           char **strs = g_strsplit (str, "\n", -1);
 
-          json_builder_begin_array (builder);
+          gtk_json_printer_start_array (printer, "comment");
           for (int i = 0; strs[i]; i++)
-            json_builder_add_string_value (builder, strs[i]);
-          json_builder_end_array (builder);
+            gtk_json_printer_add_string (printer, NULL, strs[i]);
+          gtk_json_printer_end (printer);
 
           g_strfreev (strs);
         }
       else
-        json_builder_add_string_value (builder, str);
+        gtk_json_printer_add_string (printer, "comment", str);
     }
 
-  json_builder_set_member_name (builder, "text");
-  json_builder_add_string_value (builder, layout->text);
+  gtk_json_printer_add_string (printer, "text", layout->text);
 
-  add_attr_list (builder, layout->attrs);
+  add_attr_list (printer, layout->attrs);
 
   if (layout->font_desc)
     {
       char *str = pango_font_description_to_string (layout->font_desc);
-      json_builder_set_member_name (builder, "font");
-      json_builder_add_string_value (builder, str);
+      gtk_json_printer_add_string (printer, "font", str);
       g_free (str);
     }
 
-  add_tab_array (builder, layout->tabs);
+  add_tab_array (printer, layout->tabs);
 
   if (layout->justify)
-    {
-      json_builder_set_member_name (builder, "justify");
-      json_builder_add_boolean_value (builder, TRUE);
-    }
+    gtk_json_printer_add_boolean (printer, "justify", TRUE);
 
   if (layout->justify_last_line)
-    {
-      json_builder_set_member_name (builder, "justify-last-line");
-      json_builder_add_boolean_value (builder, TRUE);
-    }
+    gtk_json_printer_add_boolean (printer, "justify-last-line", TRUE);
 
   if (layout->single_paragraph)
-    {
-      json_builder_set_member_name (builder, "single-paragraph");
-      json_builder_add_boolean_value (builder, TRUE);
-    }
+    gtk_json_printer_add_boolean (printer, "single-paragraph", TRUE);
 
   if (!layout->auto_dir)
-    {
-      json_builder_set_member_name (builder, "auto-dir");
-      json_builder_add_boolean_value (builder, FALSE);
-    }
+    gtk_json_printer_add_boolean (printer, "auto-dir", FALSE);
 
   if (layout->alignment != PANGO_ALIGN_LEFT)
-    {
-      json_builder_set_member_name (builder, "alignment");
-      add_enum_value (builder, PANGO_TYPE_ALIGNMENT, layout->alignment, FALSE);
-    }
+    add_enum_value (printer, "alignment", PANGO_TYPE_ALIGNMENT, layout->alignment, FALSE);
 
   if (layout->wrap != PANGO_WRAP_WORD)
-    {
-      json_builder_set_member_name (builder, "wrap");
-      add_enum_value (builder, PANGO_TYPE_WRAP_MODE, layout->wrap, FALSE);
-    }
+    add_enum_value (printer, "wrap", PANGO_TYPE_WRAP_MODE, layout->wrap, FALSE);
 
   if (layout->ellipsize != PANGO_ELLIPSIZE_NONE)
-    {
-      json_builder_set_member_name (builder, "ellipsize");
-      add_enum_value (builder, PANGO_TYPE_ELLIPSIZE_MODE, layout->ellipsize, FALSE);
-    }
+    add_enum_value (printer, "ellipsize", PANGO_TYPE_ELLIPSIZE_MODE, layout->ellipsize, FALSE);
 
   if (layout->width != -1)
-    {
-      json_builder_set_member_name (builder, "width");
-      json_builder_add_int_value (builder, layout->width);
-    }
+    gtk_json_printer_add_integer (printer, "width", layout->width);
 
   if (layout->height != -1)
-    {
-      json_builder_set_member_name (builder, "height");
-      json_builder_add_int_value (builder, layout->height);
-    }
+    gtk_json_printer_add_integer (printer, "height", layout->height);
 
   if (layout->indent != 0)
-    {
-      json_builder_set_member_name (builder, "indent");
-      json_builder_add_int_value (builder, layout->indent);
-    }
+    gtk_json_printer_add_integer (printer, "indent", layout->indent);
 
   if (layout->spacing != 0)
-    {
-      json_builder_set_member_name (builder, "spacing");
-      json_builder_add_int_value (builder, layout->spacing);
-    }
+    gtk_json_printer_add_integer (printer, "spacing", layout->spacing);
 
   if (layout->line_spacing != 0.)
-    {
-      json_builder_set_member_name (builder, "line-spacing");
-      json_builder_add_double_value (builder, layout->line_spacing);
-    }
+    gtk_json_printer_add_number (printer, "line-spacing", layout->line_spacing);
 
   if (flags & PANGO_LAYOUT_SERIALIZE_OUTPUT)
-    {
-      json_builder_set_member_name (builder, "output");
-      add_output (builder, layout);
-    }
+    add_output (printer, layout);
 
-  json_builder_end_object (builder);
-
-  root = json_builder_get_root (builder);
-  g_object_unref (builder);
-
-  return root;
+  gtk_json_printer_end (printer);
 }
 
-static JsonNode *
-font_to_json (PangoFont *font)
+static void
+gstring_write (GtkJsonPrinter *printer,
+               const char     *s,
+               gpointer        data)
 {
-  JsonBuilder *builder;
-  JsonNode *root;
-
-  builder = json_builder_new_immutable ();
-  add_font (builder, font);
-  root = json_builder_get_root (builder);
-  g_object_unref (builder);
-
-  return root;
+  GString *str = data;
+  g_string_append (str, s);
 }
 
 /* }}} */
 /* {{{ Deserialization */
 
-static int
-get_enum_value (GType        type,
-                const char  *str,
-                gboolean     allow_extra,
-                GError     **error)
+static gboolean
+parser_get_enum_value (GtkJsonParser  *parser,
+                       GType           type,
+                       gboolean        allow_extra,
+                       int            *value,
+                       GError        **error)
 {
   GEnumClass *enum_class;
   GEnumValue *enum_value;
+  char *str = gtk_json_parser_get_string (parser);
 
   enum_class = g_type_class_ref (type);
   enum_value = g_enum_get_value_by_nick (enum_class, str);
 
   if (enum_value)
-    return enum_value->value;
+    {
+      g_free (str);
+      *value = enum_value->value;
+      return TRUE;
+    }
 
   if (allow_extra)
     {
-      gint64 value;
+      gint64 v;
       char *endp;
 
-      value = g_ascii_strtoll (str, &endp, 10);
+      v = g_ascii_strtoll (str, &endp, 10);
       if (*endp == '\0')
-        return value;
+        {
+          g_free (str);
+          *value = (int)v;
+          return TRUE;
+        }
     }
 
   g_set_error (error,
@@ -853,217 +669,247 @@ get_enum_value (GType        type,
                g_type_name (type),
                str);
 
-  return -1;
+  g_free (str);
+
+  return FALSE;
+}
+
+static gboolean
+parser_get_font_description (GtkJsonParser         *parser,
+                             PangoFontDescription **desc,
+                             GError               **error)
+{
+  char *str = gtk_json_parser_get_string (parser);
+  *desc = pango_font_description_from_string (str);
+
+  if (!*desc)
+    g_set_error (error,
+                 PANGO_LAYOUT_DESERIALIZE_ERROR,
+                 PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE,
+                 "Failed to parse font: %s", str);
+
+  g_free (str);
+
+  return *desc != NULL;
 }
 
 static PangoAttribute *
-json_to_attribute (JsonReader  *reader,
-                   GError     **error)
+attr_for_type (GtkJsonParser  *parser,
+               PangoAttrType   type,
+               int             start,
+               int             end,
+               GError        **error)
 {
-  PangoAttribute *attr = NULL;
-  PangoAttrType type = PANGO_ATTR_INVALID;
-  guint start = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
-  guint end = PANGO_ATTR_INDEX_TO_TEXT_END;
+  PangoAttribute *attr;
   PangoFontDescription *desc;
   PangoColor color;
   int value;
+  char *str;
 
-  if (!json_reader_is_object (reader))
-    {
-      g_set_error (error,
-                   PANGO_LAYOUT_DESERIALIZE_ERROR,
-                   PANGO_LAYOUT_DESERIALIZE_INVALID_SYNTAX,
-                   "Attribute must be a Json object");
-      return NULL;
-    }
-
-  if (json_reader_read_member (reader, "start"))
-    start = json_reader_get_int_value (reader);
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "end"))
-    end = json_reader_get_int_value (reader);
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "type"))
-    {
-      type = get_enum_value (PANGO_TYPE_ATTR_TYPE, json_reader_get_string_value (reader), FALSE, error);
-      if (type == -1)
-        return NULL;
-    }
-  else
+  switch (type)
     {
-      g_set_error (error,
-                   PANGO_LAYOUT_DESERIALIZE_ERROR,
-                   PANGO_LAYOUT_DESERIALIZE_MISSING_VALUE,
-                   "Attribute \"type\" missing");
-      return NULL;
-    }
-  json_reader_end_member (reader);
+    default:
+      g_assert_not_reached ();
 
-  if (!json_reader_read_member (reader, "value"))
-    {
+    case PANGO_ATTR_INVALID:
       g_set_error (error,
                    PANGO_LAYOUT_DESERIALIZE_ERROR,
-                   PANGO_LAYOUT_DESERIALIZE_MISSING_VALUE,
-                   "Attribute \"value\" missing");
+                   PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE,
+                   "Missing attribute type");
       return NULL;
-    }
-
-  switch (type)
-    {
-    default:
-    case PANGO_ATTR_INVALID:
-      g_assert_not_reached ();
 
     case PANGO_ATTR_LANGUAGE:
-      attr = pango_attr_language_new (pango_language_from_string (json_reader_get_string_value (reader)));
+      str = gtk_json_parser_get_string (parser);
+      attr = pango_attr_language_new (pango_language_from_string (str));
+      g_free (str);
       break;
+
     case PANGO_ATTR_FAMILY:
-      attr = pango_attr_family_new (json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      attr = pango_attr_family_new (str);
+      g_free (str);
       break;
+
     case PANGO_ATTR_STYLE:
-      value = get_enum_value (PANGO_TYPE_STYLE, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_STYLE, FALSE, &value, error))
         return NULL;
       attr = pango_attr_style_new ((PangoStyle)value);
       break;
+
     case PANGO_ATTR_WEIGHT:
-      value = get_enum_value (PANGO_TYPE_WEIGHT, json_reader_get_string_value (reader), TRUE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_WEIGHT, FALSE, &value, error))
         return NULL;
       attr = pango_attr_weight_new (value);
       break;
+
     case PANGO_ATTR_VARIANT:
-      value = get_enum_value (PANGO_TYPE_VARIANT, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_VARIANT, FALSE, &value, error))
         return NULL;
       attr = pango_attr_variant_new ((PangoVariant)value);
       break;
+
     case PANGO_ATTR_STRETCH:
-      value = get_enum_value (PANGO_TYPE_STRETCH, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_STRETCH, FALSE, &value, error))
+        return NULL;
       attr = pango_attr_stretch_new ((PangoStretch)value);
       break;
+
     case PANGO_ATTR_SIZE:
-      attr = pango_attr_size_new (json_reader_get_int_value (reader));
+      attr = pango_attr_size_new (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_FONT_DESC:
-      desc = pango_font_description_from_string (json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      desc = pango_font_description_from_string (str);
       attr = pango_attr_font_desc_new (desc);
       pango_font_description_free (desc);
+      g_free (str);
       break;
+
     case PANGO_ATTR_FOREGROUND:
-      pango_color_parse (&color, json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      pango_color_parse (&color, str);
       attr = pango_attr_foreground_new (color.red, color.green, color.blue);
+      g_free (str);
       break;
+
     case PANGO_ATTR_BACKGROUND:
-      pango_color_parse (&color, json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      pango_color_parse (&color, str);
       attr = pango_attr_background_new (color.red, color.green, color.blue);
+      g_free (str);
       break;
+
     case PANGO_ATTR_UNDERLINE:
-      value = get_enum_value (PANGO_TYPE_UNDERLINE, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_UNDERLINE, FALSE, &value, error))
         return NULL;
       attr = pango_attr_underline_new ((PangoUnderline)value);
       break;
+
     case PANGO_ATTR_STRIKETHROUGH:
-      attr = pango_attr_strikethrough_new (json_reader_get_boolean_value (reader));
+      attr = pango_attr_strikethrough_new (gtk_json_parser_get_boolean (parser));
       break;
+
     case PANGO_ATTR_RISE:
-      attr = pango_attr_rise_new (json_reader_get_int_value (reader));
+      attr = pango_attr_rise_new (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_SHAPE:
       /* FIXME */
       attr = pango_attr_shape_new (&(PangoRectangle) { 0, 0, 0, 0}, &(PangoRectangle) { 0, 0, 0, 0});
       break;
+
     case PANGO_ATTR_SCALE:
-      attr = pango_attr_scale_new (json_reader_get_double_value (reader));
+      attr = pango_attr_scale_new (gtk_json_parser_get_number (parser));
       break;
+
     case PANGO_ATTR_FALLBACK:
-      attr = pango_attr_fallback_new (json_reader_get_boolean_value (reader));
+      attr = pango_attr_fallback_new (gtk_json_parser_get_boolean (parser));
       break;
+
     case PANGO_ATTR_LETTER_SPACING:
-      attr = pango_attr_letter_spacing_new (json_reader_get_int_value (reader));
+      attr = pango_attr_letter_spacing_new (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_UNDERLINE_COLOR:
-      pango_color_parse (&color, json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      pango_color_parse (&color, str);
       attr = pango_attr_underline_color_new (color.red, color.green, color.blue);
+      g_free (str);
       break;
+
     case PANGO_ATTR_STRIKETHROUGH_COLOR:
-      pango_color_parse (&color, json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      pango_color_parse (&color, str);
       attr = pango_attr_strikethrough_color_new (color.red, color.green, color.blue);
+      g_free (str);
       break;
+
     case PANGO_ATTR_ABSOLUTE_SIZE:
-      attr = pango_attr_size_new_absolute (json_reader_get_int_value (reader));
+      attr = pango_attr_size_new_absolute (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_GRAVITY:
-      value = get_enum_value (PANGO_TYPE_GRAVITY, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_GRAVITY, FALSE, &value, error))
         return NULL;
       attr = pango_attr_gravity_new ((PangoGravity)value);
       break;
+
     case PANGO_ATTR_GRAVITY_HINT:
-      value = get_enum_value (PANGO_TYPE_GRAVITY_HINT, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_GRAVITY_HINT, FALSE, &value, error))
         return NULL;
       attr = pango_attr_gravity_hint_new ((PangoGravityHint)value);
       break;
+
     case PANGO_ATTR_FONT_FEATURES:
-      attr = pango_attr_font_features_new (json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      attr = pango_attr_font_features_new (str);
+      g_free (str);
       break;
+
     case PANGO_ATTR_FOREGROUND_ALPHA:
-      attr = pango_attr_foreground_alpha_new (json_reader_get_int_value (reader));
+      attr = pango_attr_foreground_alpha_new (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_BACKGROUND_ALPHA:
-      attr = pango_attr_background_alpha_new (json_reader_get_int_value (reader));
+      attr = pango_attr_background_alpha_new (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_ALLOW_BREAKS:
-      attr = pango_attr_allow_breaks_new (json_reader_get_boolean_value (reader));
+      attr = pango_attr_allow_breaks_new (gtk_json_parser_get_boolean (parser));
       break;
+
     case PANGO_ATTR_SHOW:
-      attr = pango_attr_show_new (json_reader_get_int_value (reader));
+      attr = pango_attr_show_new (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_INSERT_HYPHENS:
-      attr = pango_attr_insert_hyphens_new (json_reader_get_boolean_value (reader));
+      attr = pango_attr_insert_hyphens_new (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_OVERLINE:
-      value = get_enum_value (PANGO_TYPE_OVERLINE, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_OVERLINE, FALSE, &value, error))
         return NULL;
       attr = pango_attr_overline_new ((PangoOverline)value);
       break;
+
     case PANGO_ATTR_OVERLINE_COLOR:
-      pango_color_parse (&color, json_reader_get_string_value (reader));
+      str = gtk_json_parser_get_string (parser);
+      pango_color_parse (&color, str);
       attr = pango_attr_overline_color_new (color.red, color.green, color.blue);
+      g_free (str);
       break;
+
     case PANGO_ATTR_LINE_HEIGHT:
-      attr = pango_attr_line_height_new (json_reader_get_double_value (reader));
+      attr = pango_attr_line_height_new (gtk_json_parser_get_number (parser));
       break;
+
     case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
-      attr = pango_attr_line_height_new_absolute (json_reader_get_int_value (reader));
+      attr = pango_attr_line_height_new_absolute (gtk_json_parser_get_int (parser));
       break;
+
     case PANGO_ATTR_TEXT_TRANSFORM:
-      value = get_enum_value (PANGO_TYPE_TEXT_TRANSFORM, json_reader_get_string_value (reader), FALSE, 
error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_TEXT_TRANSFORM, FALSE, &value, error))
         return NULL;
       attr = pango_attr_text_transform_new ((PangoTextTransform)value);
       break;
+
     case PANGO_ATTR_WORD:
       attr = pango_attr_word_new ();
       break;
+
     case PANGO_ATTR_SENTENCE:
       attr = pango_attr_sentence_new ();
       break;
+
     case PANGO_ATTR_BASELINE_SHIFT:
-      value = get_enum_value (PANGO_TYPE_BASELINE_SHIFT, json_reader_get_string_value (reader), TRUE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_BASELINE_SHIFT, FALSE, &value, error))
         return NULL;
       attr = pango_attr_baseline_shift_new (value);
       break;
+
     case PANGO_ATTR_FONT_SCALE:
-      value = get_enum_value (PANGO_TYPE_FONT_SCALE, json_reader_get_string_value (reader), FALSE, error);
-      if (value == -1)
+      if (!parser_get_enum_value (parser, PANGO_TYPE_FONT_SCALE, FALSE, &value, error))
         return NULL;
       attr = pango_attr_font_scale_new ((PangoFontScale)value);
       break;
@@ -1072,417 +918,529 @@ json_to_attribute (JsonReader  *reader,
   attr->start_index = start;
   attr->end_index = end;
 
-  json_reader_end_member (reader);
+  gtk_json_parser_end (parser);
 
   return attr;
 }
 
-static PangoAttrList *
-json_to_attr_list (JsonReader  *reader,
-                   GError     **error)
-{
-  PangoAttrList *attributes;
-
-  attributes = pango_attr_list_new ();
+enum {
+  ATTR_START,
+  ATTR_END,
+  ATTR_TYPE,
+  ATTR_VALUE
+};
+
+static const char *attr_members[] = {
+  "start",
+  "end",
+  "type",
+  "value",
+  NULL
+};
 
-  if (!json_reader_is_array (reader))
-    {
-      g_set_error (error,
-                   PANGO_LAYOUT_DESERIALIZE_ERROR,
-                   PANGO_LAYOUT_DESERIALIZE_INVALID_SYNTAX,
-                   "\"attributes\" must be a Json array");
-      goto fail;
-    }
+static PangoAttribute *
+json_to_attribute (GtkJsonParser  *parser,
+                   GError        **error)
+{
+  PangoAttribute *attr = NULL;
+  PangoAttrType type = PANGO_ATTR_INVALID;
+  guint start = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
+  guint end = PANGO_ATTR_INDEX_TO_TEXT_END;
 
-  for (int i = 0; i < json_reader_count_elements (reader); i++)
+  if (!gtk_json_parser_start_object (parser))
     {
-      PangoAttribute *attr;
-      json_reader_read_element (reader, i);
-      attr = json_to_attribute (reader, error);
-      if (!attr)
-        goto fail;
-      pango_attr_list_insert (attributes, attr);
-      json_reader_end_element (reader);
+      g_propagate_error (error, g_error_copy (gtk_json_parser_get_error (parser)));
+      return NULL;
     }
 
-  return attributes;
-
-fail:
-  if (attributes)
-    pango_attr_list_unref (attributes);
-  return NULL;
-}
-
-static PangoTabArray *
-json_to_tab_array (JsonReader  *reader,
-                   GError     **error)
-{
-  PangoTabArray *tabs;
-  gboolean positions_in_pixels = FALSE;
-
-  if (json_reader_read_member (reader, "positions-in-pixels"))
-    positions_in_pixels = json_reader_get_boolean_value (reader);
-  json_reader_end_member (reader);
-
-  tabs = pango_tab_array_new (0, positions_in_pixels);
-
-  if (json_reader_read_member (reader, "positions"))
+  do
     {
-      if (!json_reader_is_array (reader))
-        {
-          g_set_error (error,
-                       PANGO_LAYOUT_DESERIALIZE_ERROR,
-                       PANGO_LAYOUT_DESERIALIZE_INVALID_SYNTAX,
-                       "Tab \"positions\"  must be a Json array");
-          goto fail;
-        }
+      int value;
 
-      pango_tab_array_resize (tabs, json_reader_count_elements (reader));
-      for (int i = 0; i < json_reader_count_elements (reader); i++)
+      switch (gtk_json_parser_select_member (parser, attr_members))
         {
-          int pos;
-          PangoTabAlign align = PANGO_TAB_LEFT;
-          gunichar ch = 0;
-
-          json_reader_read_element (reader, i);
-          if (json_reader_is_object (reader))
-            {
-              json_reader_read_member (reader, "position");
-              pos = json_reader_get_int_value (reader);
-              json_reader_end_member (reader);
-              json_reader_read_member (reader, "alignment");
-
-              align = get_enum_value (PANGO_TYPE_TAB_ALIGN,
-                                      json_reader_get_string_value (reader),
-                                      FALSE,
-                                      error);
-              if (align == -1)
-                goto fail;
-              json_reader_end_member (reader);
-              json_reader_read_member (reader, "decimal-point");
-              ch = json_reader_get_int_value (reader);
-              json_reader_end_member (reader);
-            }
-          else
-            {
-              pos = json_reader_get_int_value (reader);
-            }
-
-          pango_tab_array_set_tab (tabs, i, align, pos);
-          pango_tab_array_set_decimal_point (tabs, i, ch);
-          json_reader_end_element (reader);
+        case ATTR_START:
+          start = gtk_json_parser_get_int (parser);
+          break;
+
+        case ATTR_END:
+          end = gtk_json_parser_get_int (parser);
+          break;
+
+        case ATTR_TYPE:
+          if (!parser_get_enum_value (parser, PANGO_TYPE_ATTR_TYPE, FALSE, &value, error))
+            return NULL;
+          type = value;
+          break;
+
+        case ATTR_VALUE:
+          attr = attr_for_type (parser, type, start, end, error);
+          break;
+
+        default:
+          g_assert_not_reached ();
         }
     }
-  json_reader_end_member (reader);
+  while (gtk_json_parser_next (parser));
 
-  return tabs;
+  gtk_json_parser_end (parser);
 
-fail:
-  if (tabs)
-    pango_tab_array_free (tabs);
-  return NULL;
+  return attr;
 }
 
 static gboolean
-apply_json_to_context (JsonReader    *reader,
-                       PangoContext  *context,
-                       GError       **error)
+json_parser_fill_attr_list (GtkJsonParser  *parser,
+                            PangoAttrList  *attributes,
+                            GError        **error)
 {
-  if (json_reader_read_member (reader, "language"))
+  if (!gtk_json_parser_start_array (parser))
     {
-      const char *value;
-      PangoLanguage *language;
-
-      value = json_reader_get_string_value (reader);
-      language = pango_language_from_string (value);
-      pango_context_set_language (context, language);
+      g_propagate_error (error, g_error_copy (gtk_json_parser_get_error (parser)));
+      return FALSE;
     }
-  json_reader_end_member (reader);
 
-  if (json_reader_read_member (reader, "font"))
+  do
     {
-      PangoFontDescription *desc;
-
-      desc = pango_font_description_from_string (json_reader_get_string_value (reader));
-      pango_context_set_font_description (context, desc);
-      pango_font_description_free (desc);
-    }
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "base-gravity"))
-    {
-      PangoGravity gravity = get_enum_value (PANGO_TYPE_GRAVITY,
-                                             json_reader_get_string_value (reader),
-                                             FALSE,
-                                             error);
-      if (gravity == -1)
-        return FALSE;
-
-      pango_context_set_base_gravity (context, gravity);
-    }
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "gravity-hint"))
-    {
-      PangoGravityHint gravity_hint = get_enum_value (PANGO_TYPE_GRAVITY_HINT,
-                                                      json_reader_get_string_value (reader),
-                                                      FALSE,
-                                                      error);
-      if (gravity_hint == -1)
-        return FALSE;
+      PangoAttribute *attr = json_to_attribute (parser, error);
 
-      pango_context_set_gravity_hint (context, gravity_hint);
-    }
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "base-dir"))
-    {
-      PangoDirection direction = get_enum_value (PANGO_TYPE_DIRECTION,
-                                                 json_reader_get_string_value (reader),
-                                                 FALSE,
-                                                 error);
-      if (direction == -1)
+      if (!attr)
         return FALSE;
 
-      pango_context_set_base_dir (context, direction);
-    }
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "round-glyph-positions"))
-    {
-      pango_context_set_round_glyph_positions (context, json_reader_get_boolean_value (reader));
+      pango_attr_list_insert (attributes, attr);
     }
-  json_reader_end_member (reader);
+  while (gtk_json_parser_next (parser));
 
-  if (json_reader_read_member (reader, "transform"))
-    {
-      PangoMatrix m;
-
-      json_reader_read_element (reader, 0);
-      m.xx = json_reader_get_double_value (reader);
-      json_reader_end_element (reader);
-      json_reader_read_element (reader, 1);
-      m.xy = json_reader_get_double_value (reader);
-      json_reader_end_element (reader);
-      json_reader_read_element (reader, 2);
-      m.yx = json_reader_get_double_value (reader);
-      json_reader_end_element (reader);
-      json_reader_read_element (reader, 3);
-      m.yy = json_reader_get_double_value (reader);
-      json_reader_end_element (reader);
-      json_reader_read_element (reader, 4);
-      m.x0 = json_reader_get_double_value (reader);
-      json_reader_end_element (reader);
-      json_reader_read_element (reader, 5);
-      m.y0 = json_reader_get_double_value (reader);
-      json_reader_end_element (reader);
-
-      pango_context_set_matrix (context, &m);
-    }
-  json_reader_end_member (reader);
+  gtk_json_parser_end (parser);
 
   return TRUE;
 }
 
-static PangoLayout *
-json_to_layout (PangoContext                 *context,
-                JsonNode                     *node,
-                PangoLayoutDeserializeFlags   flags,
-                GError                      **error)
+enum {
+  TAB_POSITION,
+  TAB_ALIGNMENT,
+  TAB_DECIMAL_POINT
+};
+
+static const char *tab_members[] = {
+  "position",
+  "alignment",
+  "decimal-point",
+  NULL,
+};
+
+static gboolean
+json_parser_fill_tabs (GtkJsonParser  *parser,
+                       PangoTabArray  *tabs,
+                       GError        **error)
 {
-  JsonReader *reader;
-  PangoLayout *layout;
+  int index;
 
-  reader = json_reader_new (node);
-  if (!json_reader_is_object (reader))
+  if (!gtk_json_parser_start_array (parser))
     {
-      g_set_error (error,
-                   PANGO_LAYOUT_DESERIALIZE_ERROR,
-                   PANGO_LAYOUT_DESERIALIZE_INVALID_SYNTAX,
-                   "Layout must be a Json object");
-      goto fail;
+      g_propagate_error (error, g_error_copy (gtk_json_parser_get_error (parser)));
+      return FALSE;
     }
 
-  if (flags & PANGO_LAYOUT_DESERIALIZE_CONTEXT)
+  index = 0;
+  do
     {
-      if (json_reader_read_member (reader, "context"))
-        {
-          if (!apply_json_to_context (reader, context, error))
-            goto fail;
-        }
-      json_reader_end_member (reader);
-    }
-
-  layout = pango_layout_new (context);
+      int pos;
+      PangoTabAlign align = PANGO_TAB_LEFT;
+      gunichar ch = 0;
 
-  if (json_reader_read_member (reader, "comment"))
-    {
-      if (json_reader_is_array (reader))
+      if (gtk_json_parser_get_node (parser) == GTK_JSON_OBJECT)
         {
-          GString *s;
-
-          s = g_string_new ("");
-          for (int i = 0; i < json_reader_count_elements (reader); i++)
+          gtk_json_parser_start_object (parser);
+          do
             {
-              json_reader_read_element (reader, i);
-              if (s->len > 0)
-                g_string_append_c (s, '\n');
-              g_string_append (s, json_reader_get_string_value (reader));
-              json_reader_end_element (reader);
+              int value;
+
+              switch (gtk_json_parser_select_member (parser, tab_members))
+                {
+                case TAB_POSITION:
+                  pos = gtk_json_parser_get_int (parser);
+                  break;
+                case TAB_ALIGNMENT:
+                  if (!parser_get_enum_value (parser, PANGO_TYPE_TAB_ALIGN, FALSE, &value, error))
+                    return FALSE;
+                  align = value;
+                  break;
+                case TAB_DECIMAL_POINT:
+                  ch = gtk_json_parser_get_int (parser);
+                  break;
+                default:
+                  g_assert_not_reached ();
+                }
             }
+          while (gtk_json_parser_next (parser));
 
-          g_object_set_data_full (G_OBJECT (layout), "comment",
-                                  g_string_free (s, FALSE),
-                                  g_free);
+          gtk_json_parser_end (parser);
         }
       else
-        g_object_set_data_full (G_OBJECT (layout), "comment",
-                                g_strdup (json_reader_get_string_value (reader)),
-                                g_free);
+        pos = gtk_json_parser_get_int (parser);
+
+      pango_tab_array_set_tab (tabs, index, align, pos);
+      pango_tab_array_set_decimal_point (tabs, index, ch);
+      index++;
+
+      gtk_json_parser_end (parser);
     }
-  json_reader_end_member (reader);
+  while (gtk_json_parser_next (parser));
 
-  if (json_reader_read_member (reader, "text"))
-    pango_layout_set_text (layout, json_reader_get_string_value (reader), -1);
-  json_reader_end_member (reader);
+  gtk_json_parser_end (parser);
 
-  if (json_reader_read_member (reader, "attributes"))
-    {
-      PangoAttrList *attributes;
+  return TRUE;
+}
 
-      attributes = json_to_attr_list (reader, error);
+enum {
+  TABS_POSITIONS_IN_PIXELS,
+  TABS_POSITIONS
+};
 
-      if (!attributes)
-        goto fail;
+static const char *tabs_members[] = {
+  "positions-in-pixels",
+  "positions",
+  NULL
+};
 
-      pango_layout_set_attributes (layout, attributes);
-      pango_attr_list_unref (attributes);
+static gboolean
+json_parser_fill_tab_array (GtkJsonParser  *parser,
+                            PangoTabArray  *tabs,
+                            GError        **error)
+{
+  if (!gtk_json_parser_start_object (parser))
+    {
+      g_propagate_error (error, g_error_copy (gtk_json_parser_get_error (parser)));
+      return FALSE;
     }
-  json_reader_end_member (reader);
 
-  if (json_reader_read_member (reader, "font"))
+  do
     {
-      PangoFontDescription *desc;
-
-      desc = pango_font_description_from_string ( json_reader_get_string_value (reader));
-      if (!desc)
+      switch (gtk_json_parser_select_member (parser, tabs_members))
         {
-          g_set_error (error,
-                       PANGO_LAYOUT_DESERIALIZE_ERROR,
-                       PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE,
-                       "Could not parse \"%s\" value: %s",
-                       "font",
-                       json_reader_get_string_value (reader));
-          goto fail;
+        case TABS_POSITIONS_IN_PIXELS:
+          pango_tab_array_set_positions_in_pixels (tabs, gtk_json_parser_get_boolean (parser));
+          break;
+        case TABS_POSITIONS:
+          if (!json_parser_fill_tabs (parser, tabs, error))
+            return FALSE;
+          break;
+        default:
+          g_assert_not_reached ();
         }
-      pango_layout_set_font_description (layout, desc);
-      pango_font_description_free (desc);
-    }
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "tabs"))
-    {
-      PangoTabArray *tabs;
-
-      tabs = json_to_tab_array (reader, error);
-
-      if (!tabs)
-        goto fail;
-
-      pango_layout_set_tabs (layout, tabs);
-      pango_tab_array_free (tabs);
     }
-  json_reader_end_member (reader);
+  while (gtk_json_parser_next (parser));
 
-  if (json_reader_read_member (reader, "justify"))
-    pango_layout_set_justify (layout, json_reader_get_boolean_value (reader));
-  json_reader_end_member (reader);
+  gtk_json_parser_end (parser);
 
-  if (json_reader_read_member (reader, "justify-last-line"))
-    pango_layout_set_justify_last_line (layout, json_reader_get_boolean_value (reader));
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "single-paragraph"))
-    pango_layout_set_single_paragraph_mode (layout, json_reader_get_boolean_value (reader));
-  json_reader_end_member (reader);
+  return TRUE;
+}
 
-  if (json_reader_read_member (reader, "auto-dir"))
-    pango_layout_set_auto_dir (layout, json_reader_get_boolean_value (reader));
-  json_reader_end_member (reader);
+enum {
+  CONTEXT_LANGUAGE,
+  CONTEXT_FONT,
+  CONTEXT_BASE_GRAVITY,
+  CONTEXT_GRAVITY_HINT,
+  CONTEXT_BASE_DIR,
+  CONTEXT_ROUND_GLYPH_POSITIONS,
+  CONTEXT_TRANSFORM,
+};
+
+static const char *context_members[] = {
+  "language",
+  "font",
+  "base-gravity",
+  "gravity-hint",
+  "base-dir",
+  "round-glyph-positions",
+  "transform",
+  NULL,
+};
 
-  if (json_reader_read_member (reader, "alignment"))
+static gboolean
+json_parser_fill_context (GtkJsonParser  *parser,
+                          PangoContext   *context,
+                          GError        **error)
+{
+  if (!gtk_json_parser_start_object (parser))
     {
-      PangoAlignment align = get_enum_value (PANGO_TYPE_ALIGNMENT,
-                                             json_reader_get_string_value (reader),
-                                             FALSE,
-                                             error);
-      if (align == -1)
-        goto fail;
-
-      pango_layout_set_alignment (layout, align);
+      g_propagate_error (error, g_error_copy (gtk_json_parser_get_error (parser)));
+      return FALSE;
     }
 
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "wrap"))
+  do
     {
-      PangoWrapMode wrap = get_enum_value (PANGO_TYPE_WRAP_MODE,
-                                           json_reader_get_string_value (reader),
-                                           FALSE,
-                                           error);
-
-      if (wrap == -1)
-        goto fail;
+      char *str;
+      int value;
 
-      pango_layout_set_wrap (layout, wrap);
+      switch (gtk_json_parser_select_member (parser, context_members))
+        {
+        case CONTEXT_LANGUAGE:
+          str = gtk_json_parser_get_string (parser);
+          PangoLanguage *language = pango_language_from_string (str);
+          pango_context_set_language (context, language);
+          g_free (str);
+          break;
+
+        case CONTEXT_FONT:
+          {
+            PangoFontDescription *desc;
+
+            if (!parser_get_font_description (parser, &desc, error))
+              return FALSE;
+
+            pango_context_set_font_description (context, desc);
+            pango_font_description_free (desc);
+          }
+          break;
+
+        case CONTEXT_BASE_GRAVITY:
+          if (!parser_get_enum_value (parser, PANGO_TYPE_GRAVITY, FALSE, &value, error))
+            return FALSE;
+          pango_context_set_base_gravity (context, (PangoGravity)value);
+          break;
+
+        case CONTEXT_GRAVITY_HINT:
+          if (!parser_get_enum_value (parser, PANGO_TYPE_GRAVITY_HINT, FALSE, &value, error))
+            return FALSE;
+          pango_context_set_gravity_hint (context, (PangoGravityHint)value);
+          break;
+
+        case CONTEXT_BASE_DIR:
+          if (!parser_get_enum_value (parser, PANGO_TYPE_DIRECTION, FALSE, &value, error))
+            return FALSE;
+          pango_context_set_base_dir (context, (PangoDirection)value);
+          break;
+
+        case CONTEXT_ROUND_GLYPH_POSITIONS:
+          pango_context_set_round_glyph_positions (context, gtk_json_parser_get_boolean (parser));
+          break;
+
+        case CONTEXT_TRANSFORM:
+          {
+            PangoMatrix m = PANGO_MATRIX_INIT;
+
+            gtk_json_parser_start_array (parser);
+            m.xx = gtk_json_parser_get_number (parser);
+            gtk_json_parser_next (parser);
+            m.xy = gtk_json_parser_get_number (parser);
+            gtk_json_parser_next (parser);
+            m.yx = gtk_json_parser_get_number (parser);
+            gtk_json_parser_next (parser);
+            m.yy = gtk_json_parser_get_number (parser);
+            gtk_json_parser_next (parser);
+            m.x0 = gtk_json_parser_get_number (parser);
+            gtk_json_parser_next (parser);
+            m.y0 = gtk_json_parser_get_number (parser);
+            gtk_json_parser_end (parser);
+
+            pango_context_set_matrix (context, &m);
+          }
+          break;
+
+          default:
+            g_assert_not_reached ();
+        }
     }
+  while (gtk_json_parser_next (parser));
 
-  json_reader_end_member (reader);
+  gtk_json_parser_end (parser);
 
-  if (json_reader_read_member (reader, "ellipsize"))
-    {
-      PangoEllipsizeMode ellipsize = get_enum_value (PANGO_TYPE_ELLIPSIZE_MODE,
-                                                     json_reader_get_string_value (reader),
-                                                     FALSE,
-                                                     error);
+  return TRUE;
+}
 
-      if (ellipsize == -1)
-        goto fail;
+enum {
+  LAYOUT_CONTEXT,
+  LAYOUT_COMMENT,
+  LAYOUT_TEXT,
+  LAYOUT_ATTRIBUTES,
+  LAYOUT_FONT,
+  LAYOUT_TABS,
+  LAYOUT_JUSTIFY,
+  LAYOUT_JUSTIFY_LAST_LINE,
+  LAYOUT_SINGLE_PARAGRAPH,
+  LAYOUT_AUTO_DIR,
+  LAYOUT_ALIGNMENT,
+  LAYOUT_WRAP,
+  LAYOUT_ELLIPSIZE,
+  LAYOUT_WIDTH,
+  LAYOUT_HEIGHT,
+  LAYOUT_INDENT,
+  LAYOUT_SPACING,
+  LAYOUT_LINE_SPACING,
+  LAYOUT_OUTPUT
+};
+
+static const char *layout_members[] = {
+  "context",
+  "comment",
+  "text",
+  "attributes",
+  "font",
+  "tabs",
+  "justify",
+  "justify-last-line",
+  "single-paragraph",
+  "auto-dir",
+  "alignment",
+  "wrap",
+  "ellipsize",
+  "width",
+  "height",
+  "indent",
+  "spacing",
+  "line-spacing",
+  "output",
+  NULL
+};
 
-      pango_layout_set_ellipsize (layout, ellipsize);
+static gboolean
+json_parser_fill_layout (GtkJsonParser                *parser,
+                         PangoLayout                  *layout,
+                         PangoLayoutDeserializeFlags   flags,
+                         GError                      **error)
+{
+  if (!gtk_json_parser_start_object (parser))
+    {
+      g_propagate_error (error, g_error_copy (gtk_json_parser_get_error (parser)));
+      return FALSE;
     }
 
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "width"))
-    pango_layout_set_width (layout, json_reader_get_int_value (reader));
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "height"))
-    pango_layout_set_height (layout, json_reader_get_int_value (reader));
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "indent"))
-    pango_layout_set_indent (layout, json_reader_get_int_value (reader));
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "spacing"))
-    pango_layout_set_spacing (layout, json_reader_get_int_value (reader));
-  json_reader_end_member (reader);
-
-  if (json_reader_read_member (reader, "line-spacing"))
-    pango_layout_set_line_spacing (layout, json_reader_get_double_value (reader));
-  json_reader_end_member (reader);
+  do
+    {
+      char *str;
+      int value;
 
-  g_object_unref (reader);
+      switch (gtk_json_parser_select_member (parser, layout_members))
+        {
+        case LAYOUT_CONTEXT:
+          if (flags & PANGO_LAYOUT_DESERIALIZE_CONTEXT)
+            {
+              if (!json_parser_fill_context (parser, pango_layout_get_context (layout), error))
+                return FALSE;
+            }
+          break;
+
+        case LAYOUT_COMMENT:
+          str = gtk_json_parser_get_string (parser);
+          g_object_set_data_full (G_OBJECT (layout), "comment", str, g_free);
+          g_free (str);
+          break;
+
+        case LAYOUT_TEXT:
+          str = gtk_json_parser_get_string (parser);
+          pango_layout_set_text (layout, str, -1);
+          g_free (str);
+          break;
+
+        case LAYOUT_ATTRIBUTES:
+          {
+            PangoAttrList *attributes = pango_attr_list_new ();
+
+            if (!json_parser_fill_attr_list (parser, attributes, error))
+              {
+                pango_attr_list_unref (attributes);
+                return FALSE;
+              }
+
+            pango_layout_set_attributes (layout, attributes);
+            pango_attr_list_unref (attributes);
+          }
+          break;
+
+        case LAYOUT_FONT:
+          {
+            PangoFontDescription *desc;
+
+            if (!parser_get_font_description (parser, &desc, error))
+              return FALSE;
+
+            pango_layout_set_font_description (layout, desc);
+            pango_font_description_free (desc);
+          }
+          break;
+
+        case LAYOUT_TABS:
+          {
+            PangoTabArray *tabs = pango_tab_array_new (0, FALSE);
+
+            if (!json_parser_fill_tab_array (parser, tabs, error))
+              {
+                pango_tab_array_free (tabs);
+                return FALSE;
+              }
+
+            pango_layout_set_tabs (layout, tabs);
+            pango_tab_array_free (tabs);
+          }
+          break;
+
+        case LAYOUT_JUSTIFY:
+          pango_layout_set_justify (layout, gtk_json_parser_get_boolean (parser));
+          break;
+
+        case LAYOUT_JUSTIFY_LAST_LINE:
+          pango_layout_set_justify_last_line (layout, gtk_json_parser_get_boolean (parser));
+          break;
+
+        case LAYOUT_SINGLE_PARAGRAPH:
+          pango_layout_set_single_paragraph_mode (layout, gtk_json_parser_get_boolean (parser));
+          break;
+
+        case LAYOUT_AUTO_DIR:
+          pango_layout_set_auto_dir (layout, gtk_json_parser_get_boolean (parser));
+          break;
+
+        case LAYOUT_ALIGNMENT:
+          if (!parser_get_enum_value (parser, PANGO_TYPE_ALIGNMENT, FALSE, &value, error))
+            return FALSE;
+          pango_layout_set_alignment (layout, (PangoAlignment)value);
+          break;
+
+        case LAYOUT_WRAP:
+          if (!parser_get_enum_value (parser, PANGO_TYPE_WRAP_MODE, FALSE, &value, error))
+            return FALSE;
+          pango_layout_set_wrap (layout, (PangoWrapMode)value);
+          break;
+
+        case LAYOUT_ELLIPSIZE:
+          if (!parser_get_enum_value (parser, PANGO_TYPE_ELLIPSIZE_MODE, FALSE, &value, error))
+            return FALSE;
+          pango_layout_set_ellipsize (layout, (PangoEllipsizeMode)value);
+          break;
+
+        case LAYOUT_WIDTH:
+          pango_layout_set_width (layout, gtk_json_parser_get_int (parser));
+          break;
+
+        case LAYOUT_HEIGHT:
+          pango_layout_set_height (layout, gtk_json_parser_get_int (parser));
+          break;
+
+        case LAYOUT_INDENT:
+          pango_layout_set_indent (layout, gtk_json_parser_get_int (parser));
+          break;
+
+        case LAYOUT_SPACING:
+          pango_layout_set_spacing (layout, gtk_json_parser_get_int (parser));
+          break;
+
+        case LAYOUT_LINE_SPACING:
+          pango_layout_set_line_spacing (layout, gtk_json_parser_get_number (parser));
+          break;
+
+        case LAYOUT_OUTPUT:
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+    }
+  while (gtk_json_parser_next (parser));
 
-  return layout;
+  gtk_json_parser_end (parser);
 
-fail:
-  g_object_unref (reader);
-  if (layout)
-    g_object_unref (layout);
-  return NULL;
+  return TRUE;
 }
 
 /* }}} */
@@ -1510,29 +1468,23 @@ GBytes *
 pango_layout_serialize (PangoLayout               *layout,
                         PangoLayoutSerializeFlags  flags)
 {
-  JsonGenerator *generator;
-  JsonNode *node;
   GString *str;
+  GtkJsonPrinter *printer;
   char *data;
   gsize size;
 
   g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
 
-  node = layout_to_json (layout, flags);
+  str = g_string_new ("");
 
-  generator = json_generator_new ();
-  json_generator_set_pretty (generator, TRUE);
-  json_generator_set_indent (generator, 2);
+  printer = gtk_json_printer_new (gstring_write, str, NULL);
+  gtk_json_printer_set_flags (printer, GTK_JSON_PRINTER_PRETTY);
+  layout_to_json (printer, layout, flags);
+  gtk_json_printer_free (printer);
 
-  json_generator_set_root (generator, node);
-  str = g_string_new ("");
-  g_string_append_c (json_generator_to_gstring (generator, str), '\n');
   size = str->len;
   data = g_string_free (str, FALSE);
 
-  json_node_free (node);
-  g_object_unref (generator);
-
   return g_bytes_new_take (data, size);
 }
 
@@ -1600,26 +1552,22 @@ pango_layout_deserialize (PangoContext                 *context,
                           PangoLayoutDeserializeFlags   flags,
                           GError                      **error)
 {
-  JsonParser *parser;
-  JsonNode *node;
   PangoLayout *layout;
+  GtkJsonParser *parser;
 
   g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL);
 
-  parser = json_parser_new_immutable ();
-  if (!json_parser_load_from_data (parser,
-                                   g_bytes_get_data (bytes, NULL),
-                                   g_bytes_get_size (bytes),
-                                   error))
+  layout = pango_layout_new (context);
+
+  parser = gtk_json_parser_new_for_bytes (bytes);
+  if (!json_parser_fill_layout (parser, layout, flags, error))
     {
-      g_object_unref (parser);
+      gtk_json_parser_free (parser);
+      g_object_unref (layout);
       return NULL;
     }
 
-  node = json_parser_get_root (parser);
-  layout = json_to_layout (context, node, flags, error);
-
-  g_object_unref (parser);
+  gtk_json_parser_free (parser);
 
   return layout;
 }
@@ -1643,24 +1591,22 @@ pango_layout_deserialize (PangoContext                 *context,
 GBytes *
 pango_font_serialize (PangoFont *font)
 {
-  JsonGenerator *generator;
-  JsonNode *node;
+  GString *str;
+  GtkJsonPrinter *printer;
   char *data;
   gsize size;
 
   g_return_val_if_fail (PANGO_IS_FONT (font), NULL);
 
-  node = font_to_json (font);
-
-  generator = json_generator_new ();
-  json_generator_set_pretty (generator, TRUE);
-  json_generator_set_indent (generator, 2);
+  str = g_string_new ("");
 
-  json_generator_set_root (generator, node);
-  data = json_generator_to_data (generator, &size);
+  printer = gtk_json_printer_new (gstring_write, str, NULL);
+  gtk_json_printer_set_flags (printer, GTK_JSON_PRINTER_PRETTY);
+  add_font (printer, NULL, font);
+  gtk_json_printer_free (printer);
 
-  json_node_free (node);
-  g_object_unref (generator);
+  size = str->len;
+  data = g_string_free (str, FALSE);
 
   return g_bytes_new_take (data, size);
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]