use resast::prelude::*;
use ressa::Parser;
use serde_json::Value as JsonValue;
use std::fmt::Write;
pub fn json_to_gron(value: &JsonValue) -> String {
let mut output = String::new();
let mut path = String::from("json");
gron_recursive(value, &mut path, &mut output);
output
}
fn gron_recursive(value: &JsonValue, path: &mut String, output: &mut String) {
match value {
JsonValue::Null => {
writeln!(output, "{} = null;", path).unwrap();
}
JsonValue::Bool(b) => {
writeln!(output, "{} = {};", path, b).unwrap();
}
JsonValue::Number(n) => {
writeln!(output, "{} = {};", path, n).unwrap();
}
JsonValue::String(s) => {
writeln!(output, "{} = {};", path, escape_string(s)).unwrap();
}
JsonValue::Array(arr) => {
writeln!(output, "{} = [];", path).unwrap();
for (i, item) in arr.iter().enumerate() {
let prefix_len = path.len();
write!(path, "[{}]", i).unwrap();
gron_recursive(item, path, output);
path.truncate(prefix_len);
}
}
JsonValue::Object(obj) => {
writeln!(output, "{} = {{}};", path).unwrap();
for (key, val) in obj.iter() {
let prefix_len = path.len();
append_path_segment(path, key);
gron_recursive(val, path, output);
path.truncate(prefix_len);
}
}
}
}
fn is_valid_identifier(s: &str) -> bool {
if s.is_empty() {
return false;
}
if s.chars().any(|c| c.is_whitespace() || c.is_control()) {
return false;
}
let test_code = format!("json.{}", s);
if let Ok(mut parser) = Parser::new(&test_code) {
if let Some(Ok(ProgramPart::Stmt(Stmt::Expr(Expr::Member(_))))) = parser.next() {
return parser.next().is_none();
}
}
false
}
fn append_path_segment(path: &mut String, key: &str) {
if is_valid_identifier(key) {
write!(path, ".{}", key).unwrap();
} else {
write!(path, "[{}]", escape_string(key)).unwrap();
}
}
fn escape_string(s: &str) -> String {
let mut result = String::with_capacity(s.len() + 2);
result.push('"');
for ch in s.chars() {
match ch {
'"' => result.push_str(r#"\""#),
'\\' => result.push_str(r"\\"),
'\n' => result.push_str(r"\n"),
'\r' => result.push_str(r"\r"),
'\t' => result.push_str(r"\t"),
'\x08' => result.push_str(r"\b"),
'\x0C' => result.push_str(r"\f"),
c if c.is_control() => {
write!(result, "\\u{:04x}", c as u32).unwrap();
}
c => result.push(c),
}
}
result.push('"');
result
}
#[cfg(test)]
#[path = "gron_test.rs"]
mod tests;