1use serde_json::{Map, Value};
8
9#[must_use]
19pub fn display(value: &Value) -> String {
20 match value {
21 Value::Null => String::new(),
22 Value::Bool(flag) => flag.to_string(),
23 Value::Number(number) => number.to_string(),
24 Value::String(text) => text.clone(),
25 Value::Array(_) | Value::Object(_) => value.to_string(),
26 }
27}
28
29#[must_use]
34pub fn truthy(value: &Value) -> bool {
35 match value {
36 Value::Null => false,
37 Value::Bool(flag) => *flag,
38 Value::Number(number) => number.as_f64().is_some_and(|number| number != 0.0),
39 Value::String(text) => !text.is_empty(),
40 Value::Array(items) => !items.is_empty(),
41 Value::Object(fields) => !fields.is_empty(),
42 }
43}
44
45#[must_use]
51pub fn to_lines(value: &Value) -> Vec<String> {
52 match value {
53 Value::Array(items) => items.iter().map(display).collect(),
54 Value::Null => Vec::new(),
55 other => {
56 let text = display(other);
57 if text.is_empty() {
58 return Vec::new();
59 }
60 let mut lines = text.split('\n').map(str::to_owned).collect::<Vec<_>>();
61 if lines.last().is_some_and(String::is_empty) {
62 lines.pop();
63 }
64 lines
65 }
66 }
67}
68
69#[must_use]
76pub fn from_lines(lines: Vec<String>) -> Value {
77 match lines.len() {
78 0 => Value::Null,
79 1 => Value::String(lines.into_iter().next().unwrap_or_default()),
80 _ => Value::Array(lines.into_iter().map(Value::String).collect()),
81 }
82}
83
84#[must_use]
86pub fn to_text(value: &Value) -> String {
87 match value {
88 Value::Array(_) => to_lines(value).join("\n"),
89 other => display(other),
90 }
91}
92
93#[must_use]
98pub fn index(value: &Value, key: &str) -> Value {
99 match value {
100 Value::Array(items) => key
101 .parse::<usize>()
102 .ok()
103 .and_then(|offset| items.get(offset))
104 .cloned()
105 .unwrap_or(Value::Null),
106 Value::Object(fields) => fields.get(key).cloned().unwrap_or(Value::Null),
107 _ => Value::Null,
108 }
109}
110
111#[must_use]
117pub fn scalar_from_token(token: &str) -> Value {
118 match token {
119 "true" => return Value::Bool(true),
120 "false" => return Value::Bool(false),
121 "null" => return Value::Null,
122 _ => {}
123 }
124 if let Ok(Value::Number(number)) = serde_json::from_str::<Value>(token) {
125 return Value::Number(number);
126 }
127 Value::String(token.to_owned())
128}
129
130#[must_use]
132pub fn object_from_pairs(pairs: Vec<(String, Value)>) -> Value {
133 let mut fields = Map::new();
134 for (key, value) in pairs {
135 match fields.remove(&key) {
136 None => {
137 fields.insert(key, value);
138 }
139 Some(Value::Array(mut existing)) => {
140 existing.push(value);
141 fields.insert(key, Value::Array(existing));
142 }
143 Some(existing) => {
144 fields.insert(key, Value::Array(vec![existing, value]));
145 }
146 }
147 }
148 Value::Object(fields)
149}
150
151#[cfg(test)]
152mod tests {
153 use serde_json::json;
154
155 use super::{
156 Value, display, from_lines, index, object_from_pairs, scalar_from_token, to_lines, truthy,
157 };
158
159 #[test]
160 fn display_uses_documented_coercions() {
161 assert_eq!(display(&json!("hi")), "hi");
162 assert_eq!(display(&json!(7)), "7");
163 assert_eq!(display(&json!(1.5)), "1.5");
164 assert_eq!(display(&json!(true)), "true");
165 assert_eq!(display(&Value::Null), "");
166 assert_eq!(display(&json!([1, 2])), "[1,2]");
167 assert_eq!(display(&json!({"a": 1})), r#"{"a":1}"#);
168 }
169
170 #[test]
171 fn truthiness_follows_the_value_model() {
172 assert!(!truthy(&Value::Null));
173 assert!(!truthy(&json!("")));
174 assert!(truthy(&json!("x")));
175 assert!(!truthy(&json!(0)));
176 assert!(truthy(&json!(3)));
177 assert!(!truthy(&json!([])));
178 assert!(truthy(&json!([1])));
179 }
180
181 #[test]
182 fn text_shaped_conversions_round_trip() {
183 assert_eq!(to_lines(&json!("a\nb")), vec!["a", "b"]);
184 assert_eq!(to_lines(&json!("a\nb\n")), vec!["a", "b"]);
185 assert_eq!(to_lines(&json!(["a", "b"])), vec!["a", "b"]);
186 assert_eq!(to_lines(&Value::Null), Vec::<String>::new());
187 assert_eq!(from_lines(vec!["only".to_owned()]), json!("only"));
188 assert_eq!(
189 from_lines(vec!["a".to_owned(), "b".to_owned()]),
190 json!(["a", "b"])
191 );
192 assert_eq!(from_lines(Vec::new()), Value::Null);
194 }
195
196 #[test]
197 fn indexing_is_backed_by_real_json() {
198 assert_eq!(index(&json!([10, 20]), "1"), json!(20));
199 assert_eq!(index(&json!([10, 20]), "9"), Value::Null);
200 assert_eq!(index(&json!({"key": "v"}), "key"), json!("v"));
201 assert_eq!(index(&json!("scalar"), "0"), Value::Null);
202 }
203
204 #[test]
205 fn argv_tokens_promote_only_unambiguous_scalars() {
206 assert_eq!(scalar_from_token("7"), json!(7));
207 assert_eq!(scalar_from_token("-1.5"), json!(-1.5));
208 assert_eq!(scalar_from_token("true"), json!(true));
209 assert_eq!(scalar_from_token("null"), Value::Null);
210 assert_eq!(scalar_from_token("hello"), json!("hello"));
211 assert_eq!(scalar_from_token(r#"{"a":1}"#), json!(r#"{"a":1}"#));
212 }
213
214 #[test]
215 fn repeated_object_keys_fold_into_arrays() {
216 let object = object_from_pairs(vec![
217 ("headerName".to_owned(), json!("a")),
218 ("headerName".to_owned(), json!("b")),
219 ("other".to_owned(), json!(1)),
220 ]);
221 assert_eq!(object, json!({"headerName": ["a", "b"], "other": 1}));
222 }
223}