1use crate::fs;
2use serde::Serialize;
3use serde::de::DeserializeOwned;
4use std::fmt::Debug;
5use std::path::Path;
6use tracing::{instrument, trace};
7
8pub use crate::json_error::JsonError;
9pub use serde_json;
10pub use serde_json::{Map as JsonMap, Number as JsonNumber, Value as JsonValue, json};
11
12#[inline]
14#[instrument(name = "clean_json", skip_all)]
15pub fn clean<T: AsRef<str>>(json: T) -> Result<String, std::io::Error> {
16 let mut json = json.as_ref().to_owned();
17
18 if !json.is_empty() {
19 json_strip_comments::strip(&mut json)?;
20 }
21
22 Ok(json)
23}
24
25#[inline]
27#[instrument(name = "merge_json", skip_all)]
28pub fn merge(prev: &JsonValue, next: &JsonValue) -> JsonValue {
29 match (prev, next) {
30 (JsonValue::Object(prev_object), JsonValue::Object(next_object)) => {
31 let mut object = prev_object.clone();
32
33 for (key, value) in next_object.iter() {
34 if let Some(prev_value) = prev_object.get(key) {
35 object.insert(key.to_owned(), merge(prev_value, value));
36 } else {
37 object.insert(key.to_owned(), value.to_owned());
38 }
39 }
40
41 JsonValue::Object(object)
42 }
43 _ => next.to_owned(),
44 }
45}
46
47#[inline]
49#[instrument(name = "parse_json", skip(data))]
50pub fn parse<D>(data: impl AsRef<str>) -> Result<D, JsonError>
51where
52 D: DeserializeOwned,
53{
54 trace!("Parsing JSON");
55
56 let contents = clean(data.as_ref()).map_err(|error| JsonError::Clean {
57 error: Box::new(error),
58 })?;
59
60 serde_json::from_str(&contents).map_err(|error| JsonError::Parse {
61 error: Box::new(error),
62 })
63}
64
65#[inline]
67#[instrument(name = "format_json", skip(data))]
68pub fn format<D>(data: &D, pretty: bool) -> Result<String, JsonError>
69where
70 D: ?Sized + Serialize,
71{
72 trace!("Formatting JSON");
73
74 if pretty {
75 serde_json::to_string_pretty(&data).map_err(|error| JsonError::Format {
76 error: Box::new(error),
77 })
78 } else {
79 serde_json::to_string(&data).map_err(|error| JsonError::Format {
80 error: Box::new(error),
81 })
82 }
83}
84
85#[inline]
88#[instrument(name = "format_json_with_identation", skip(data))]
89pub fn format_with_identation<D>(data: &D, indent: &str) -> Result<String, JsonError>
90where
91 D: ?Sized + Serialize,
92{
93 use serde_json::Serializer;
94 use serde_json::ser::PrettyFormatter;
95
96 trace!(indent, "Formatting JSON with preserved indentation");
97
98 let mut writer = Vec::with_capacity(128);
100 let mut serializer =
101 Serializer::with_formatter(&mut writer, PrettyFormatter::with_indent(indent.as_bytes()));
102
103 data.serialize(&mut serializer)
104 .map_err(|error| JsonError::Format {
105 error: Box::new(error),
106 })?;
107
108 Ok(unsafe { String::from_utf8_unchecked(writer) })
109}
110
111#[inline]
114#[instrument(name = "read_json")]
115pub fn read_file<D>(path: impl AsRef<Path> + Debug) -> Result<D, JsonError>
116where
117 D: DeserializeOwned,
118{
119 let path = path.as_ref();
120 let contents = clean(fs::read_file(path)?).map_err(|error| JsonError::CleanFile {
121 path: path.to_owned(),
122 error: Box::new(error),
123 })?;
124
125 trace!(file = ?path, "Reading JSON file");
126
127 serde_json::from_str(&contents).map_err(|error| JsonError::ReadFile {
128 path: path.to_path_buf(),
129 error: Box::new(error),
130 })
131}
132
133#[inline]
138#[instrument(name = "write_json", skip(data))]
139pub fn write_file<D>(
140 path: impl AsRef<Path> + Debug,
141 data: &D,
142 pretty: bool,
143) -> Result<(), JsonError>
144where
145 D: ?Sized + Serialize,
146{
147 let path = path.as_ref();
148
149 trace!(file = ?path, "Writing JSON file");
150
151 let data = if pretty {
152 serde_json::to_string_pretty(&data).map_err(|error| JsonError::WriteFile {
153 path: path.to_path_buf(),
154 error: Box::new(error),
155 })?
156 } else {
157 serde_json::to_string(&data).map_err(|error| JsonError::WriteFile {
158 path: path.to_path_buf(),
159 error: Box::new(error),
160 })?
161 };
162
163 fs::write_file(path, data)?;
164
165 Ok(())
166}
167
168#[cfg(feature = "editor-config")]
174#[inline]
175#[instrument(name = "write_json_with_config", skip(data))]
176pub fn write_file_with_config<D>(
177 path: impl AsRef<Path> + Debug,
178 data: &D,
179 pretty: bool,
180) -> Result<(), JsonError>
181where
182 D: ?Sized + Serialize,
183{
184 if !pretty {
185 return write_file(path, &data, false);
186 }
187
188 trace!(file = ?path, "Writing JSON file with .editorconfig");
189
190 let path = path.as_ref();
191 let editor_config = fs::get_editor_config_props(path)?;
192
193 let mut data = format_with_identation(&data, &editor_config.indent)?;
194 editor_config.apply_eof(&mut data);
195
196 fs::write_file(path, data)?;
197
198 Ok(())
199}