type json =
  [ `Null
  | `Bool of bool
  | `Float of float
  | `String of string
  | `Array of json list
  | `Object of (string * json) list ]

let rec buffer_add_json b = function
  | `Null -> Buffer.add_string b "null"
  | `Bool bool -> Buffer.add_string b (if bool then "true" else "false")
  | `Float f -> Buffer.add_string b (Printf.sprintf "%.16g" f)
  | `String s -> buffer_add_json_string b s
  | `Array els -> (
      match els with
      | [] -> Buffer.add_string b "[]"
      | el :: els ->
          let add_sep_el b e =
            Buffer.add_char b ',';
            buffer_add_json b e
          in
          Buffer.add_char b '[';
          buffer_add_json b el;
          List.iter (add_sep_el b) els;
          Buffer.add_char b ']')
  | `Object mems -> (
      match mems with
      | [] -> Buffer.add_string b "{}"
      | mem :: mems ->
          let add_mem b (k, v) =
            buffer_add_json_string b k;
            Buffer.add_char b ':';
            buffer_add_json b v
          in
          let add_sep_mem b mem =
            Buffer.add_char b ',';
            add_mem b mem
          in
          Buffer.add_char b '{';
          add_mem b mem;
          List.iter (add_sep_mem b) mems;
          Buffer.add_char b '}')

and buffer_add_json_string b s =
  let is_control = function '\x00' .. '\x1F' | '\x7F' -> true | _ -> false in
  let len = String.length s in
  let max_idx = len - 1 in
  let flush b start i =
    if start < len then Buffer.add_substring b s start (i - start)
  in
  let rec loop start i =
    match i > max_idx with
    | true -> flush b start i
    | false -> (
        let next = i + 1 in
        match String.get s i with
        | '"' ->
            flush b start i;
            Buffer.add_string b "\\\"";
            loop next next
        | '\\' ->
            flush b start i;
            Buffer.add_string b "\\\\";
            loop next next
        | c when is_control c ->
            flush b start i;
            Buffer.add_string b (Printf.sprintf "\\u%04X" (Char.code c));
            loop next next
        | _c -> loop start next)
  in
  Buffer.add_char b '"';
  loop 0 0;
  Buffer.add_char b '"'

let to_string json =
  let b = Buffer.create 1024 in
  buffer_add_json b json;
  Buffer.contents b
