use crate::model::value::Value;
use crate::text::write_string;
const ONE_LINE: usize = 72;
pub(super) const INDENT: &str = " ";
pub fn format(value: &Value, level: usize) -> String {
if let Some(one) = inline(value) {
return one;
}
let pad = INDENT.repeat(level + 1);
let end = INDENT.repeat(level);
match value {
Value::List(items) => {
let body: Vec<String> = items
.iter()
.map(|v| format!("{pad}{}", format(v, level + 1)))
.collect();
format!("[\n{}\n{end}]", body.join(",\n"))
}
Value::Map(map) => {
let body: Vec<String> = map
.iter()
.map(|(k, v)| format!("{pad}{}: {}", quoted(k), format(v, level + 1)))
.collect();
format!("{{\n{}\n{end}}}", body.join(",\n"))
}
other => scalar(other).unwrap_or_default(),
}
}
pub(super) fn quoted(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
write_string(s, &mut out);
out
}
pub(super) fn number(n: f64) -> String {
crate::text::float_string(n)
}
fn scalar(v: &Value) -> Option<String> {
match v {
Value::Bool(b) => Some(b.to_string()),
Value::Int(n) => Some(n.to_string()),
Value::Float(f) if f.is_finite() => Some(number(*f)),
Value::Null | Value::Float(_) => Some("null".into()),
Value::List(_) | Value::Map(_) => None,
Value::Str(s) => Some(quoted(s)),
other => Some(crate::text::to_string(other)),
}
}
fn inline(v: &Value) -> Option<String> {
if let Some(s) = scalar(v) {
return Some(s);
}
let out = match v {
Value::List(items) if items.is_empty() => return Some("[]".into()),
Value::List(items) => {
if !items
.iter()
.all(|x| matches!(x, Value::Int(_) | Value::Float(_) | Value::Bool(_)))
{
return None;
}
format!(
"[{}]",
items
.iter()
.filter_map(scalar)
.collect::<Vec<_>>()
.join(", ")
)
}
Value::Map(map) if map.is_empty() => return Some("{}".into()),
Value::Map(map) => {
let mut parts = Vec::with_capacity(map.len());
for (k, x) in map.iter() {
if matches!(x, Value::Map(_)) {
return None;
}
parts.push(format!("{}: {}", quoted(k), inline(x)?));
}
format!("{{ {} }}", parts.join(", "))
}
_ => return None,
};
(out.len() <= ONE_LINE).then_some(out)
}
pub fn to_json(value: &Value) -> String {
let mut out = String::new();
json_into(value, &mut out);
out
}
fn json_into(value: &Value, out: &mut String) {
match value {
Value::List(items) => {
out.push('[');
for (i, v) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
json_into(v, out);
}
out.push(']');
}
Value::Map(map) => {
out.push('{');
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_string(k, out);
out.push(':');
json_into(v, out);
}
out.push('}');
}
Value::Str(s) => write_string(s, out),
Value::Float(f) if f.is_finite() => out.push_str(&f.to_string()),
Value::Float(_) => out.push_str("null"),
other => out.push_str(&crate::text::to_string(other)),
}
}
pub fn same(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Int(_) | Value::Float(_), Value::Int(_) | Value::Float(_)) => {
match (a.as_f64(), b.as_f64()) {
(Some(x), Some(y)) => x.to_bits() == y.to_bits(),
_ => false,
}
}
(Value::List(x), Value::List(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(p, q)| same(p, q))
}
(Value::Map(x), Value::Map(y)) => {
x.len() == y.len()
&& x.iter()
.zip(y.iter())
.all(|((ka, p), (kb, q))| ka == kb && same(p, q))
}
_ => a == b,
}
}