blob: 079f98fe6b020444b59ac6ef033e529311763847 [file] [log] [blame]
use crate::JsonValue;
use std::collections::HashMap;
use std::fmt;
use std::io::{self, Write};
/// Serialization error. This error only happens when some write error happens on writing the serialized byte sequence
/// to the given `io::Write` object.
#[derive(Debug)]
pub struct JsonGenerateError {
msg: String,
}
impl JsonGenerateError {
pub fn message(&self) -> &str {
self.msg.as_str()
}
}
impl fmt::Display for JsonGenerateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Generate error: {}", &self.msg)
}
}
impl std::error::Error for JsonGenerateError {}
/// Convenient type alias for serialization results.
pub type JsonGenerateResult = Result<String, JsonGenerateError>;
/// JSON serializer for `JsonValue`.
///
/// Basically you don't need to use this struct directly since `JsonValue::stringify` or `JsonValue::format` methods are
/// using this internally.
///
/// ```
/// use tinyjson::{JsonGenerator, JsonValue};
///
/// let v = JsonValue::from("hello, world".to_string());
/// let mut buf = vec![];
/// let mut gen = JsonGenerator::new(&mut buf);
/// gen.generate(&v).unwrap();
///
/// assert_eq!(String::from_utf8(buf).unwrap(), "\"hello, world\"");
/// ```
pub struct JsonGenerator<'indent, W: Write> {
out: W,
indent: Option<&'indent str>,
}
impl<'indent, W: Write> JsonGenerator<'indent, W> {
/// Create a new `JsonGenerator` object. The serialized byte sequence will be written to the given `io::Write`
/// object.
pub fn new(out: W) -> Self {
Self { out, indent: None }
}
/// Set indent string. This will be used by [`JsonGenerator::generate`].
/// ```
/// use tinyjson::{JsonGenerator, JsonValue};
///
/// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
/// let mut buf = vec![];
/// let mut gen = JsonGenerator::new(&mut buf).indent(" ");
/// gen.generate(&v).unwrap();
///
/// assert_eq!(String::from_utf8(buf).unwrap(),
/// "[
/// 1,
/// 2,
/// 3
/// ]");
/// ```
pub fn indent(mut self, indent: &'indent str) -> Self {
self.indent = Some(indent);
self
}
fn quote(&mut self, s: &str) -> io::Result<()> {
const B: u8 = b'b'; // \x08
const T: u8 = b't'; // \x09
const N: u8 = b'n'; // \x0a
const F: u8 = b'f'; // \x0c
const R: u8 = b'r'; // \x0d
const Q: u8 = b'"'; // \x22
const S: u8 = b'\\'; // \x5c
const U: u8 = 1; // non-printable
#[rustfmt::skip]
const ESCAPE_TABLE: [u8; 256] = [
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
U, U, U, U, U, U, U, U, B, T, N, U, F, R, U, U, // 0
U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, // 1
0, 0, Q, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, S, 0, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
];
self.out.write_all(b"\"")?;
let mut start = 0;
for (i, c) in s.char_indices() {
let u = c as usize;
if u < 256 {
let esc = ESCAPE_TABLE[u];
if esc == 0 {
continue;
}
if start != i {
self.out.write_all(s[start..i].as_bytes())?;
}
if esc == U {
write!(self.out, "\\u{:04x}", u)?;
} else {
self.out.write_all(&[b'\\', esc])?;
}
start = i + 1;
}
}
if start != s.len() {
self.out.write_all(s[start..].as_bytes())?;
}
self.out.write_all(b"\"")
}
fn number(&mut self, f: f64) -> io::Result<()> {
if f.is_infinite() {
Err(io::Error::new(
io::ErrorKind::Other,
"JSON cannot represent inf",
))
} else if f.is_nan() {
Err(io::Error::new(
io::ErrorKind::Other,
"JSON cannot represent NaN",
))
} else {
write!(self.out, "{}", f)
}
}
fn encode_array(&mut self, array: &[JsonValue]) -> io::Result<()> {
self.out.write_all(b"[")?;
let mut first = true;
for elem in array.iter() {
if first {
first = false;
} else {
self.out.write_all(b",")?;
}
self.encode(elem)?;
}
self.out.write_all(b"]")
}
fn encode_object(&mut self, m: &HashMap<String, JsonValue>) -> io::Result<()> {
self.out.write_all(b"{")?;
let mut first = true;
for (k, v) in m {
if first {
first = false;
} else {
self.out.write_all(b",")?;
}
self.quote(k)?;
self.out.write_all(b":")?;
self.encode(v)?;
}
self.out.write_all(b"}")
}
fn encode(&mut self, value: &JsonValue) -> io::Result<()> {
match value {
JsonValue::Number(n) => self.number(*n),
JsonValue::Boolean(b) => write!(self.out, "{}", *b),
JsonValue::String(s) => self.quote(s),
JsonValue::Null => self.out.write_all(b"null"),
JsonValue::Array(a) => self.encode_array(a),
JsonValue::Object(o) => self.encode_object(o),
}
}
fn write_indent(&mut self, indent: &str, level: usize) -> io::Result<()> {
for _ in 0..level {
self.out.write_all(indent.as_bytes())?;
}
Ok(())
}
fn format_array(&mut self, array: &[JsonValue], indent: &str, level: usize) -> io::Result<()> {
if array.is_empty() {
return self.out.write_all(b"[]");
}
self.out.write_all(b"[\n")?;
let mut first = true;
for elem in array.iter() {
if first {
first = false;
} else {
self.out.write_all(b",\n")?;
}
self.write_indent(indent, level + 1)?;
self.format(elem, indent, level + 1)?;
}
self.out.write_all(b"\n")?;
self.write_indent(indent, level)?;
self.out.write_all(b"]")
}
fn format_object(
&mut self,
m: &HashMap<String, JsonValue>,
indent: &str,
level: usize,
) -> io::Result<()> {
if m.is_empty() {
return self.out.write_all(b"{}");
}
self.out.write_all(b"{\n")?;
let mut first = true;
for (k, v) in m {
if first {
first = false;
} else {
self.out.write_all(b",\n")?;
}
self.write_indent(indent, level + 1)?;
self.quote(k)?;
self.out.write_all(b": ")?;
self.format(v, indent, level + 1)?;
}
self.out.write_all(b"\n")?;
self.write_indent(indent, level)?;
self.out.write_all(b"}")
}
fn format(&mut self, value: &JsonValue, indent: &str, level: usize) -> io::Result<()> {
match value {
JsonValue::Number(n) => self.number(*n),
JsonValue::Boolean(b) => write!(self.out, "{}", *b),
JsonValue::String(s) => self.quote(s),
JsonValue::Null => self.out.write_all(b"null"),
JsonValue::Array(a) => self.format_array(a, indent, level),
JsonValue::Object(o) => self.format_object(o, indent, level),
}
}
/// Serialize the `JsonValue` into UTF-8 byte sequence. The result will be written to the `io::Write` object passed
/// at [`JsonGenerator::new`].
/// This method serializes the value without indentation by default. But after setting an indent string via
/// [`JsonGenerator::indent`], this method will use the indent for elements of array and object.
///
/// ```
/// use tinyjson::{JsonGenerator, JsonValue};
///
/// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
///
/// let mut buf = vec![];
/// let mut gen = JsonGenerator::new(&mut buf);
/// gen.generate(&v).unwrap();
/// assert_eq!(String::from_utf8(buf).unwrap(), "[1,2,3]");
///
/// let mut buf = vec![];
/// let mut gen = JsonGenerator::new(&mut buf).indent(" "); // with 2-spaces indent
/// gen.generate(&v).unwrap();
///
/// assert_eq!(String::from_utf8(buf).unwrap(),
/// "[
/// 1,
/// 2,
/// 3
/// ]");
/// ```
pub fn generate(&mut self, value: &JsonValue) -> io::Result<()> {
if let Some(indent) = &self.indent {
self.format(value, indent, 0)
} else {
self.encode(value)
}
}
}
/// Serialize the given `JsonValue` value to `String` without indentation. This method is almost identical to
/// `JsonValue::stringify` but exists for a historical reason.
///
/// ```
/// use tinyjson::JsonValue;
///
/// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
/// let s = tinyjson::stringify(&v).unwrap();
/// assert_eq!(s, "[1,2,3]");
/// ```
pub fn stringify(value: &JsonValue) -> JsonGenerateResult {
let mut to = Vec::new();
let mut gen = JsonGenerator::new(&mut to);
gen.generate(value).map_err(|err| JsonGenerateError {
msg: format!("{}", err),
})?;
Ok(String::from_utf8(to).unwrap())
}
/// Serialize the given `JsonValue` value to `String` with 2-spaces indentation. This method is almost identical to
/// `JsonValue::format` but exists for a historical reason.
///
/// ```
/// use tinyjson::JsonValue;
///
/// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
/// let s = tinyjson::format(&v).unwrap();
/// assert_eq!(s, "[\n 1,\n 2,\n 3\n]");
/// ```
pub fn format(value: &JsonValue) -> JsonGenerateResult {
let mut to = Vec::new();
let mut gen = JsonGenerator::new(&mut to).indent(" ");
gen.generate(value).map_err(|err| JsonGenerateError {
msg: format!("{}", err),
})?;
Ok(String::from_utf8(to).unwrap())
}