use std::fmt::{Display, Write};
use crate::content::Content;
const COMPACT_MAX_CHARS: usize = 120;
pub fn format_float<T: Display>(value: T) -> String {
let mut rv = format!("{}", value);
if !rv.contains('.') {
rv.push_str(".0");
}
rv
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum Format {
Condensed,
SingleLine,
Pretty,
}
pub struct Serializer {
out: String,
format: Format,
indentation: usize,
}
impl Serializer {
pub fn new() -> Serializer {
Serializer {
out: String::new(),
format: Format::Condensed,
indentation: 0,
}
}
pub fn into_result(self) -> String {
self.out
}
fn write_indentation(&mut self) {
if self.format == Format::Pretty {
write!(self.out, "{: ^1$}", "", self.indentation * 2).unwrap();
}
}
fn start_container(&mut self, c: char) {
self.write_char(c);
self.indentation += 1;
}
fn end_container(&mut self, c: char, empty: bool) {
self.indentation -= 1;
if self.format == Format::Pretty && !empty {
self.write_char('\n');
self.write_indentation();
}
self.write_char(c);
}
fn write_comma(&mut self, first: bool) {
match self.format {
Format::Pretty => {
if first {
self.write_char('\n');
} else {
self.write_str(",\n");
}
self.write_indentation();
}
Format::Condensed => {
if !first {
self.write_char(',');
}
}
Format::SingleLine => {
if !first {
self.write_str(", ");
}
}
}
}
fn write_colon(&mut self) {
match self.format {
Format::Pretty | Format::SingleLine => self.write_str(": "),
Format::Condensed => self.write_char(':'),
}
}
fn serialize_array(&mut self, items: &[Content]) {
self.start_container('[');
for (idx, item) in items.iter().enumerate() {
self.write_comma(idx == 0);
self.serialize(item);
}
self.end_container(']', items.is_empty());
}
fn serialize_object(&mut self, fields: &[(&str, Content)]) {
self.start_container('{');
for (idx, (key, value)) in fields.iter().enumerate() {
self.write_comma(idx == 0);
self.write_escaped_str(key);
self.write_colon();
self.serialize(value);
}
self.end_container('}', fields.is_empty());
}
pub fn serialize(&mut self, value: &Content) {
match value {
Content::Bool(true) => self.write_str("true"),
Content::Bool(false) => self.write_str("false"),
Content::U8(n) => write!(self.out, "{}", n).unwrap(),
Content::U16(n) => write!(self.out, "{}", n).unwrap(),
Content::U32(n) => write!(self.out, "{}", n).unwrap(),
Content::U64(n) => write!(self.out, "{}", n).unwrap(),
Content::U128(n) => write!(self.out, "{}", n).unwrap(),
Content::I8(n) => write!(self.out, "{}", n).unwrap(),
Content::I16(n) => write!(self.out, "{}", n).unwrap(),
Content::I32(n) => write!(self.out, "{}", n).unwrap(),
Content::I64(n) => write!(self.out, "{}", n).unwrap(),
Content::I128(n) => write!(self.out, "{}", n).unwrap(),
Content::F32(f) => {
if f.is_finite() {
self.write_str(&format_float(f));
} else {
self.write_str("null")
}
}
Content::F64(f) => {
if f.is_finite() {
self.write_str(&format_float(f));
} else {
self.write_str("null")
}
}
Content::Char(c) => self.write_escaped_str(&(*c).to_string()),
Content::String(s) => self.write_escaped_str(s),
Content::Bytes(bytes) => {
self.start_container('[');
for (idx, byte) in bytes.iter().enumerate() {
self.write_comma(idx == 0);
self.write_str(&byte.to_string());
}
self.end_container(']', bytes.is_empty());
}
Content::None | Content::Unit | Content::UnitStruct(_) => self.write_str("null"),
Content::Some(content) => self.serialize(content),
Content::UnitVariant(_, _, variant) => self.write_escaped_str(variant),
Content::NewtypeStruct(_, content) => self.serialize(content),
Content::NewtypeVariant(_, _, variant, content) => {
self.start_container('{');
self.write_comma(true);
self.write_escaped_str(variant);
self.write_colon();
self.serialize(content);
self.end_container('}', false);
}
Content::Seq(seq) | Content::Tuple(seq) | Content::TupleStruct(_, seq) => {
self.serialize_array(seq);
}
Content::TupleVariant(_, _, variant, seq) => {
self.start_container('{');
self.write_comma(true);
self.write_escaped_str(variant);
self.write_colon();
self.serialize_array(seq);
self.end_container('}', false);
}
Content::Map(map) => {
self.start_container('{');
for (idx, (key, value)) in map.iter().enumerate() {
self.write_comma(idx == 0);
let real_key = key.resolve_inner();
if let Content::String(ref s) = real_key {
self.write_escaped_str(s);
} else if let Some(num) = real_key.as_i64() {
self.write_escaped_str(&num.to_string());
} else if let Some(num) = real_key.as_i128() {
self.write_escaped_str(&num.to_string());
} else {
panic!("cannot serialize maps without string keys to JSON");
}
self.write_colon();
self.serialize(value);
}
self.end_container('}', map.is_empty());
}
Content::Struct(_, fields) => {
self.serialize_object(fields);
}
Content::StructVariant(_, _, variant, fields) => {
self.start_container('{');
self.write_comma(true);
self.write_escaped_str(variant);
self.write_colon();
self.serialize_object(fields);
self.end_container('}', false);
}
}
}
fn write_str(&mut self, s: &str) {
self.out.push_str(s);
}
fn write_char(&mut self, c: char) {
self.out.push(c);
}
fn write_escaped_str(&mut self, value: &str) {
self.write_char('"');
let bytes = value.as_bytes();
let mut start = 0;
for (i, &byte) in bytes.iter().enumerate() {
let escape = ESCAPE[byte as usize];
if escape == 0 {
continue;
}
if start < i {
self.write_str(&value[start..i]);
}
match escape {
self::BB => self.write_str("\\b"),
self::TT => self.write_str("\\t"),
self::NN => self.write_str("\\n"),
self::FF => self.write_str("\\f"),
self::RR => self.write_str("\\r"),
self::QU => self.write_str("\\\""),
self::BS => self.write_str("\\\\"),
self::U => {
static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
self.write_str("\\u00");
self.write_char(HEX_DIGITS[(byte >> 4) as usize] as char);
self.write_char(HEX_DIGITS[(byte & 0xF) as usize] as char);
}
_ => unreachable!(),
}
start = i + 1;
}
if start != bytes.len() {
self.write_str(&value[start..]);
}
self.write_char('"');
}
}
const BB: u8 = b'b'; const TT: u8 = b't'; const NN: u8 = b'n'; const FF: u8 = b'f'; const RR: u8 = b'r'; const QU: u8 = b'"'; const BS: u8 = b'\\'; const U: u8 = b'u'; #[rustfmt::skip]
static ESCAPE: [u8; 256] = [
U, U, U, U, U, U, U, U, BB, TT, NN, U, FF, RR, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, 0, 0, QU, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, BS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
pub fn to_string(value: &Content) -> String {
let mut ser = Serializer::new();
ser.serialize(value);
ser.into_result()
}
#[allow(unused)]
pub fn to_string_compact(value: &Content) -> String {
let mut ser = Serializer::new();
ser.format = Format::SingleLine;
ser.serialize(value);
let rv = ser.into_result();
if rv.chars().count() > COMPACT_MAX_CHARS {
to_string_pretty(value)
} else {
rv
}
}
#[allow(unused)]
pub fn to_string_pretty(value: &Content) -> String {
let mut ser = Serializer::new();
ser.format = Format::Pretty;
ser.serialize(value);
ser.into_result()
}
#[test]
fn test_to_string() {
let json = to_string(&Content::Map(vec![
(
Content::from("environments"),
Content::Seq(vec![
Content::from("development"),
Content::from("production"),
]),
),
(Content::from("cmdline"), Content::Seq(vec![])),
(Content::from("extra"), Content::Map(vec![])),
]));
crate::assert_snapshot!(&json, @r###"{"environments":["development","production"],"cmdline":[],"extra":{}}"###);
}
#[test]
fn test_to_string_pretty() {
let json = to_string_pretty(&Content::Map(vec![
(
Content::from("environments"),
Content::Seq(vec![
Content::from("development"),
Content::from("production"),
]),
),
(Content::from("cmdline"), Content::Seq(vec![])),
(Content::from("extra"), Content::Map(vec![])),
]));
crate::assert_snapshot!(&json, @r###"
{
"environments": [
"development",
"production"
],
"cmdline": [],
"extra": {}
}
"###);
}
#[test]
fn test_to_string_num_keys() {
let content = Content::Map(vec![
(Content::from(42u32), Content::from(true)),
(Content::from(-23i32), Content::from(false)),
]);
let json = to_string_pretty(&content);
crate::assert_snapshot!(&json, @r###"
{
"42": true,
"-23": false
}
"###);
}
#[test]
fn test_to_string_pretty_complex() {
let content = Content::Map(vec![
(
Content::from("is_alive"),
Content::NewtypeStruct("Some", Content::from(true).into()),
),
(
Content::from("newtype_variant"),
Content::NewtypeVariant(
"Foo",
0,
"variant_a",
Box::new(Content::Struct(
"VariantA",
vec![
("field_a", Content::String("value_a".into())),
("field_b", 42u32.into()),
],
)),
),
),
(
Content::from("struct_variant"),
Content::StructVariant(
"Foo",
0,
"variant_b",
vec![
("field_a", Content::String("value_a".into())),
("field_b", 42u32.into()),
],
),
),
(
Content::from("tuple_variant"),
Content::TupleVariant(
"Foo",
0,
"variant_c",
vec![(Content::String("value_a".into())), (42u32.into())],
),
),
(Content::from("empty_array"), Content::Seq(vec![])),
(Content::from("empty_object"), Content::Map(vec![])),
(Content::from("array"), Content::Seq(vec![true.into()])),
(
Content::from("object"),
Content::Map(vec![("foo".into(), true.into())]),
),
(
Content::from("array_of_objects"),
Content::Seq(vec![Content::Struct(
"MyType",
vec![
("foo", Content::from("bar".to_string())),
("bar", Content::from("xxx".to_string())),
],
)]),
),
(
Content::from("unit_variant"),
Content::UnitVariant("Stuff", 0, "value"),
),
(Content::from("u8"), Content::U8(8)),
(Content::from("u16"), Content::U16(16)),
(Content::from("u32"), Content::U32(32)),
(Content::from("u64"), Content::U64(64)),
(Content::from("u128"), Content::U128(128)),
(Content::from("i8"), Content::I8(8)),
(Content::from("i16"), Content::I16(16)),
(Content::from("i32"), Content::I32(32)),
(Content::from("i64"), Content::I64(64)),
(Content::from("i128"), Content::I128(128)),
(Content::from("f32"), Content::F32(32.0)),
(Content::from("f64"), Content::F64(64.0)),
(Content::from("char"), Content::Char('A')),
(Content::from("bytes"), Content::Bytes(b"hehe".to_vec())),
(Content::from("null"), Content::None),
(Content::from("unit"), Content::Unit),
(
Content::from("crazy_string"),
Content::String((0u8..=126).map(|x| x as char).collect()),
),
]);
let json = to_string_pretty(&content);
crate::assert_snapshot!(&json, @r###"
{
"is_alive": true,
"newtype_variant": {
"variant_a": {
"field_a": "value_a",
"field_b": 42
}
},
"struct_variant": {
"variant_b": {
"field_a": "value_a",
"field_b": 42
}
},
"tuple_variant": {
"variant_c": [
"value_a",
42
]
},
"empty_array": [],
"empty_object": {},
"array": [
true
],
"object": {
"foo": true
},
"array_of_objects": [
{
"foo": "bar",
"bar": "xxx"
}
],
"unit_variant": "value",
"u8": 8,
"u16": 16,
"u32": 32,
"u64": 64,
"u128": 128,
"i8": 8,
"i16": 16,
"i32": 32,
"i64": 64,
"i128": 128,
"f32": 32.0,
"f64": 64.0,
"char": "A",
"bytes": [
104,
101,
104,
101
],
"null": null,
"unit": null,
"crazy_string": "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
}
"###);
}