1use crate::error::Error;
4use crate::options::EncodeOptions;
5use serde_json::Value;
6
7pub fn encode(value: &Value, options: Option<&EncodeOptions>) -> Result<String, Error> {
18 let default_opts = EncodeOptions::default();
19 let opts = options.unwrap_or(&default_opts);
20 let mut output = String::new();
21 encode_value(value, &mut output, 0, opts)?;
22 Ok(output)
23}
24
25fn encode_value(
26 value: &Value,
27 output: &mut String,
28 indent_level: usize,
29 options: &EncodeOptions,
30) -> Result<(), Error> {
31 match value {
32 Value::Null => {
33 }
35 Value::Bool(b) => {
36 output.push_str(if *b { "true" } else { "false" });
37 }
38 Value::Number(n) => {
39 if let Some(i) = n.as_i64() {
40 output.push_str(&i.to_string());
41 } else if let Some(f) = n.as_f64() {
42 output.push_str(&f.to_string());
43 } else {
44 return Err(Error::Serialization("Invalid number".to_string()));
45 }
46 }
47 Value::String(s) => {
48 encode_string(s, output, options.get_delimiter());
49 }
50 Value::Array(arr) => {
51 encode_array(arr, output, indent_level, options)?;
52 }
53 Value::Object(obj) => {
54 encode_object(obj, output, indent_level, options)?;
55 }
56 }
57 Ok(())
58}
59
60fn encode_string(s: &str, output: &mut String, delimiter: char) {
61 let needs_quoting = s.contains(delimiter)
63 || s.contains(' ')
64 || s.contains('\n')
65 || s.contains('\t')
66 || s == "true"
67 || s == "false"
68 || s == "null"
69 || s.parse::<f64>().is_ok();
70
71 if needs_quoting {
72 output.push('"');
73 for ch in s.chars() {
74 match ch {
75 '"' => output.push_str("\\\""),
76 '\\' => output.push_str("\\\\"),
77 '\n' => output.push_str("\\n"),
78 '\r' => output.push_str("\\r"),
79 '\t' => output.push_str("\\t"),
80 _ => output.push(ch),
81 }
82 }
83 output.push('"');
84 } else {
85 output.push_str(s);
86 }
87}
88
89fn encode_array(
90 arr: &[Value],
91 output: &mut String,
92 indent_level: usize,
93 options: &EncodeOptions,
94) -> Result<(), Error> {
95 if arr.is_empty() {
96 output.push_str("[0]:");
97 return Ok(());
98 }
99
100 if let Some(keys) = check_uniform_objects(arr) {
102 let length_marker = options
104 .length_marker
105 .map(|m| format!("{m}"))
106 .unwrap_or_default();
107 output.push_str(&format!("[{}{}]", length_marker, arr.len()));
108 output.push('{');
109 output.push_str(&keys.join(&options.get_delimiter().to_string()));
110 output.push_str("}:\n");
111 encode_tabular_array_rows(arr, keys, output, indent_level, options)?;
112 return Ok(());
113 }
114
115 if arr.iter().all(is_primitive) {
117 encode_inline_array(arr, output, options)?;
118 return Ok(());
119 }
120
121 encode_list_array(arr, output, indent_level, options)?;
123 Ok(())
124}
125
126fn is_primitive(value: &Value) -> bool {
127 matches!(
128 value,
129 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
130 )
131}
132
133fn check_uniform_objects(arr: &[Value]) -> Option<Vec<String>> {
134 if arr.is_empty() {
135 return None;
136 }
137
138 let first = arr[0].as_object()?;
140 let keys: Vec<String> = first.keys().cloned().collect();
141 if keys.is_empty() {
142 return None;
143 }
144
145 for item in arr.iter().skip(1) {
147 let obj = item.as_object()?;
148 let item_keys: std::collections::HashSet<String> = obj.keys().cloned().collect();
149 let first_keys: std::collections::HashSet<String> = keys.iter().cloned().collect();
150 if item_keys != first_keys {
151 return None;
152 }
153 }
154
155 Some(keys)
156}
157
158fn encode_tabular_array_rows(
159 arr: &[Value],
160 keys: Vec<String>,
161 output: &mut String,
162 indent_level: usize,
163 options: &EncodeOptions,
164) -> Result<(), Error> {
165 let indent = options.get_indent();
166 let indent_str = " ".repeat(indent_level * indent);
167 let delimiter = options.get_delimiter();
168
169 for item in arr {
171 output.push_str(&indent_str);
172 output.push_str(&" ".repeat(indent));
173 let obj = item
174 .as_object()
175 .ok_or_else(|| Error::Serialization("Expected object in tabular array".to_string()))?;
176
177 let mut first = true;
178 for key in &keys {
179 if !first {
180 output.push(delimiter);
181 }
182 let value = obj
183 .get(key)
184 .ok_or_else(|| Error::Serialization(format!("Missing key: {key}")))?;
185 encode_primitive_value(value, output, delimiter)?;
186 first = false;
187 }
188 output.push('\n');
189 }
190
191 Ok(())
192}
193
194fn encode_primitive_value(
195 value: &Value,
196 output: &mut String,
197 delimiter: char,
198) -> Result<(), Error> {
199 match value {
200 Value::Null => {}
201 Value::Bool(b) => {
202 output.push_str(if *b { "true" } else { "false" });
203 }
204 Value::Number(n) => {
205 if let Some(i) = n.as_i64() {
206 output.push_str(&i.to_string());
207 } else if let Some(f) = n.as_f64() {
208 output.push_str(&f.to_string());
209 } else {
210 return Err(Error::Serialization("Invalid number".to_string()));
211 }
212 }
213 Value::String(s) => {
214 encode_string(s, output, delimiter);
215 }
216 _ => {
217 return Err(Error::Serialization(
218 "Non-primitive value in tabular array".to_string(),
219 ));
220 }
221 }
222 Ok(())
223}
224
225fn encode_inline_array(
226 arr: &[Value],
227 output: &mut String,
228 options: &EncodeOptions,
229) -> Result<(), Error> {
230 let length_marker = options
231 .length_marker
232 .map(|m| format!("{m}"))
233 .unwrap_or_default();
234 output.push_str(&format!("[{}{}]:", length_marker, arr.len()));
235
236 let delimiter = options.get_delimiter();
237 let mut first = true;
238 for item in arr {
239 if !first {
240 output.push(delimiter);
241 }
242 match item {
243 Value::Null => {}
244 Value::Bool(b) => {
245 output.push_str(if *b { "true" } else { "false" });
246 }
247 Value::Number(n) => {
248 if let Some(i) = n.as_i64() {
249 output.push_str(&i.to_string());
250 } else if let Some(f) = n.as_f64() {
251 output.push_str(&f.to_string());
252 }
253 }
254 Value::String(s) => {
255 encode_string(s, output, delimiter);
256 }
257 _ => {
258 return Err(Error::Serialization(
259 "Non-primitive in inline array".to_string(),
260 ));
261 }
262 }
263 first = false;
264 }
265
266 Ok(())
267}
268
269fn encode_list_array(
270 arr: &[Value],
271 output: &mut String,
272 indent_level: usize,
273 options: &EncodeOptions,
274) -> Result<(), Error> {
275 let indent = options.get_indent();
276 let indent_str = " ".repeat(indent_level * indent);
277
278 for item in arr {
279 output.push_str(&indent_str);
280 output.push_str(&" ".repeat(indent));
281 output.push_str("- ");
282 match item {
284 Value::Object(obj) => {
285 let mut first = true;
286 for (key, val) in obj {
287 if !first {
288 output.push(' ');
289 }
290 output.push_str(key);
291 output.push_str(": ");
292 encode_primitive_value(val, output, options.get_delimiter())?;
293 first = false;
294 }
295 }
296 _ => {
297 encode_value(item, output, indent_level + 1, options)?;
298 }
299 }
300 output.push('\n');
301 }
302
303 Ok(())
304}
305
306fn encode_object(
307 obj: &serde_json::Map<String, Value>,
308 output: &mut String,
309 indent_level: usize,
310 options: &EncodeOptions,
311) -> Result<(), Error> {
312 if obj.is_empty() {
313 return Ok(());
314 }
315
316 let indent = options.get_indent();
317 let indent_str = " ".repeat(indent_level * indent);
318
319 let mut first = true;
320 for (key, value) in obj {
321 if !first {
322 output.push('\n');
323 }
324 output.push_str(&indent_str);
325 output.push_str(key);
326
327 match value {
328 Value::Array(arr) => {
329 if arr.is_empty() {
331 output.push_str("[0]:");
332 } else if let Some(keys) = check_uniform_objects(arr) {
333 let length_marker = options
335 .length_marker
336 .map(|m| format!("{m}"))
337 .unwrap_or_default();
338 output.push_str(&format!("[{}{}]", length_marker, arr.len()));
339 output.push('{');
340 output.push_str(&keys.join(&options.get_delimiter().to_string()));
341 output.push_str("}:\n");
342 encode_tabular_array_rows(arr, keys, output, indent_level, options)?;
344 } else if arr.iter().all(is_primitive) {
345 let length_marker = options
347 .length_marker
348 .map(|m| format!("{m}"))
349 .unwrap_or_default();
350 output.push_str(&format!("[{}{}]:", length_marker, arr.len()));
351 let delimiter = options.get_delimiter();
352 let mut first = true;
353 for item in arr {
354 if !first {
355 output.push(delimiter);
356 }
357 encode_primitive_value(item, output, delimiter)?;
358 first = false;
359 }
360 } else {
361 let length_marker = options
363 .length_marker
364 .map(|m| format!("{m}"))
365 .unwrap_or_default();
366 output.push_str(&format!("[{}{}]:", length_marker, arr.len()));
367 output.push('\n');
368 encode_list_array(arr, output, indent_level, options)?;
369 }
370 }
371 Value::Object(_) => {
372 output.push_str(": ");
373 output.push('\n');
374 encode_value(value, output, indent_level + 1, options)?;
375 }
376 _ => {
377 output.push_str(": ");
378 encode_value(value, output, indent_level, options)?;
379 }
380 }
381 first = false;
382 }
383
384 Ok(())
385}