use crate::fs;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::fmt::Debug;
use std::path::Path;
use tracing::{instrument, trace};
pub use crate::json_error::JsonError;
pub use serde_json;
pub use serde_json::{Map as JsonMap, Number as JsonNumber, Value as JsonValue, json};
#[inline]
#[instrument(name = "clean_json", skip_all)]
pub fn clean<T: AsRef<str>>(json: T) -> Result<String, std::io::Error> {
let mut json = json.as_ref().to_owned();
if !json.is_empty() {
json_strip_comments::strip(&mut json)?;
}
Ok(json)
}
#[inline]
#[instrument(name = "merge_json", skip_all)]
pub fn merge(prev: &JsonValue, next: &JsonValue) -> JsonValue {
match (prev, next) {
(JsonValue::Object(prev_object), JsonValue::Object(next_object)) => {
let mut object = prev_object.clone();
for (key, value) in next_object.iter() {
if let Some(prev_value) = prev_object.get(key) {
object.insert(key.to_owned(), merge(prev_value, value));
} else {
object.insert(key.to_owned(), value.to_owned());
}
}
JsonValue::Object(object)
}
_ => next.to_owned(),
}
}
#[inline]
#[instrument(name = "parse_json", skip(data))]
pub fn parse<D>(data: impl AsRef<str>) -> Result<D, JsonError>
where
D: DeserializeOwned,
{
trace!("Parsing JSON");
let contents = clean(data.as_ref()).map_err(|error| JsonError::Clean {
error: Box::new(error),
})?;
serde_json::from_str(&contents).map_err(|error| JsonError::Parse {
error: Box::new(error),
})
}
#[inline]
#[instrument(name = "format_json", skip(data))]
pub fn format<D>(data: &D, pretty: bool) -> Result<String, JsonError>
where
D: ?Sized + Serialize,
{
trace!("Formatting JSON");
if pretty {
serde_json::to_string_pretty(&data).map_err(|error| JsonError::Format {
error: Box::new(error),
})
} else {
serde_json::to_string(&data).map_err(|error| JsonError::Format {
error: Box::new(error),
})
}
}
#[inline]
#[instrument(name = "format_json_with_identation", skip(data))]
pub fn format_with_identation<D>(data: &D, indent: &str) -> Result<String, JsonError>
where
D: ?Sized + Serialize,
{
use serde_json::Serializer;
use serde_json::ser::PrettyFormatter;
trace!(indent, "Formatting JSON with preserved indentation");
let mut writer = Vec::with_capacity(128);
let mut serializer =
Serializer::with_formatter(&mut writer, PrettyFormatter::with_indent(indent.as_bytes()));
data.serialize(&mut serializer)
.map_err(|error| JsonError::Format {
error: Box::new(error),
})?;
Ok(unsafe { String::from_utf8_unchecked(writer) })
}
#[inline]
#[instrument(name = "read_json")]
pub fn read_file<D>(path: impl AsRef<Path> + Debug) -> Result<D, JsonError>
where
D: DeserializeOwned,
{
let path = path.as_ref();
let contents = clean(fs::read_file(path)?).map_err(|error| JsonError::CleanFile {
path: path.to_owned(),
error: Box::new(error),
})?;
trace!(file = ?path, "Reading JSON file");
serde_json::from_str(&contents).map_err(|error| JsonError::ReadFile {
path: path.to_path_buf(),
error: Box::new(error),
})
}
#[inline]
#[instrument(name = "write_json", skip(data))]
pub fn write_file<D>(
path: impl AsRef<Path> + Debug,
data: &D,
pretty: bool,
) -> Result<(), JsonError>
where
D: ?Sized + Serialize,
{
let path = path.as_ref();
trace!(file = ?path, "Writing JSON file");
let data = if pretty {
serde_json::to_string_pretty(&data).map_err(|error| JsonError::WriteFile {
path: path.to_path_buf(),
error: Box::new(error),
})?
} else {
serde_json::to_string(&data).map_err(|error| JsonError::WriteFile {
path: path.to_path_buf(),
error: Box::new(error),
})?
};
fs::write_file(path, data)?;
Ok(())
}
#[cfg(feature = "editor-config")]
#[inline]
#[instrument(name = "write_json_with_config", skip(data))]
pub fn write_file_with_config<D>(
path: impl AsRef<Path> + Debug,
data: &D,
pretty: bool,
) -> Result<(), JsonError>
where
D: ?Sized + Serialize,
{
if !pretty {
return write_file(path, &data, false);
}
trace!(file = ?path, "Writing JSON file with .editorconfig");
let path = path.as_ref();
let editor_config = fs::get_editor_config_props(path)?;
let mut data = format_with_identation(&data, &editor_config.indent)?;
editor_config.apply_eof(&mut data);
fs::write_file(path, data)?;
Ok(())
}