///|
pub fn ThemeDocument::to_json(
  self : ThemeDocument,
  pretty? : Bool = true,
) -> String {
  let document = self.sorted()
  let builder = StringBuilder::new()
  builder.write_string("{")
  if pretty && document.tokens.length() > 0 {
    builder.write_string("\n")
  }
  for i, token in document.tokens {
    if i > 0 {
      builder.write_string(",")
      if pretty {
        builder.write_string("\n")
      }
    }
    if pretty {
      builder.write_string("  ")
    }
    builder.write_string("\"")
    builder.write_string(json_escape(token.path.canonical()))
    builder.write_string("\": ")
    builder.write_string(theme_value_json(token.value))
  }
  if pretty && document.tokens.length() > 0 {
    builder.write_string("\n")
  }
  builder.write_string("}")
  builder.to_string()
}

///|
fn theme_value_json(value : ThemeValue) -> String {
  match value {
    ColorValue(text) => "\"" + json_escape(text) + "\""
    NumberValue(number) => number.to_string()
    TextValue(text) => "\"" + json_escape(text) + "\""
    BooleanValue(flag) => if flag { "true" } else { "false" }
  }
}

///|
pub fn json_escape(text : String) -> String {
  let builder = StringBuilder::new()
  for ch in text {
    match ch {
      '"' => builder.write_string("\\\"")
      '\\' => builder.write_string("\\\\")
      '\n' => builder.write_string("\\n")
      '\r' => builder.write_string("\\r")
      '\t' => builder.write_string("\\t")
      _ => builder.write_char(ch)
    }
  }
  builder.to_string()
}