use serde::{ser::SerializeMap, Serializer as _};
use serde_json::{Serializer as JsonSerializer, Value};
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
#[allow(non_camel_case_types)]
#[derive(Debug)]
pub struct json_writer {
pub buf: String,
pub file: String,
pub first: bool,
fields: Vec<(String, Value)>,
}
impl json_writer {
pub fn new() -> Self {
Self {
buf: String::new(),
file: String::new(),
first: true,
fields: Vec::new(),
}
}
pub fn init(&mut self, filename: &str, append: bool) -> io::Result<()> {
if filename.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"empty filename",
));
}
self.file.clear();
self.file.push_str(filename);
if !append {
let _ = File::create(&self.file)?;
}
Ok(())
}
pub fn reset(&mut self) {
self.buf.clear();
self.first = true;
self.fields.clear();
}
#[inline]
fn sep(&mut self) {
self.first = false;
}
fn push_field(&mut self, key: &str, value: Value) {
self.sep();
self.fields.push((key.to_string(), value));
self.buf.clear();
}
fn render(&self) -> io::Result<String> {
let mut out = Vec::new();
{
let mut serializer = JsonSerializer::new(&mut out);
let mut map = serializer
.serialize_map(Some(self.fields.len()))
.map_err(io::Error::other)?;
for (key, value) in &self.fields {
map.serialize_entry(key, value).map_err(io::Error::other)?;
}
map.end().map_err(io::Error::other)?;
}
String::from_utf8(out).map_err(io::Error::other)
}
pub fn field_str(&mut self, k: &str, v: &str) {
self.push_field(k, Value::String(v.to_string()));
}
pub fn field_u64(&mut self, k: &str, v: u64) {
self.push_field(k, Value::String(v.to_string()));
}
pub fn field_i32(&mut self, k: &str, v: i32) {
self.push_field(k, Value::String(v.to_string()));
}
pub fn field_f32(&mut self, k: &str, v: f32) {
self.push_field(k, Value::String(format!("{v:.6}")));
}
pub fn finalize(&mut self) {
self.buf = self.render().unwrap_or_else(|_| "{}".to_string());
}
pub fn dump(&mut self) -> io::Result<()> {
if self.file.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"json_writer not initialized",
));
}
if self.buf.is_empty() {
self.buf = self.render()?;
}
let mut out = OpenOptions::new()
.create(true)
.append(true)
.open(&self.file)?;
out.write_all(self.buf.as_bytes())?;
out.write_all(b"\n")?;
Ok(())
}
}
impl Default for json_writer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::json_writer;
#[test]
fn serde_backend_preserves_field_order_and_string_schema() {
let mut writer = json_writer::new();
writer.reset();
writer.field_str("name", "sender");
writer.field_str("quoted", "line\n\"two\"");
writer.field_u64("time", 42);
writer.field_i32("delta", -7);
writer.field_f32("rate", 1.5);
writer.finalize();
assert_eq!(
writer.buf,
"{\"name\":\"sender\",\"quoted\":\"line\\n\\\"two\\\"\",\"time\":\"42\",\"delta\":\"-7\",\"rate\":\"1.500000\"}"
);
}
}