mongosh/formatter/
json.rs1use colored_json::prelude::*;
10use mongodb::bson::{Bson, Document};
11
12use crate::error::Result;
13use crate::executor::ResultData;
14
15pub struct JsonFormatter {
17 pretty: bool,
19
20 indent: usize,
22
23 use_colors: bool,
25}
26
27impl JsonFormatter {
28 pub fn new(pretty: bool, use_colors: bool, indent: usize) -> Self {
37 Self {
38 pretty,
39 indent: indent,
40 use_colors,
41 }
42 }
43
44 pub fn format(&self, data: &ResultData) -> Result<String> {
52 match data {
53 ResultData::Documents(docs) => self.format_documents(docs),
54 ResultData::DocumentsWithPagination { documents, .. } => {
55 self.format_documents(documents)
56 }
57 ResultData::Document(doc) => self.format_document(doc),
58 ResultData::Message(msg) => Ok(format!("\"{}\"", msg)),
59 ResultData::List(items) => {
60 let list_str = items.join("\n");
61 Ok(list_str)
62 }
63 ResultData::InsertOne { inserted_id } => {
64 Ok(format!("{{ \"insertedId\": \"{}\" }}", inserted_id))
65 }
66 ResultData::InsertMany { inserted_ids } => {
67 let ids_json = inserted_ids
68 .iter()
69 .map(|id| format!("\"{}\"", id))
70 .collect::<Vec<_>>()
71 .join(", ");
72 Ok(format!("{{ \"insertedIds\": [{}] }}", ids_json))
73 }
74 ResultData::Update { matched, modified } => Ok(format!(
75 "{{ \"matchedCount\": {}, \"modifiedCount\": {} }}",
76 matched, modified
77 )),
78 ResultData::Delete { deleted } => Ok(format!("{{ \"deletedCount\": {} }}", deleted)),
79 ResultData::Count(count) => Ok(format!("{}", count)),
80 ResultData::None => Ok("null".to_string()),
81 }
82 }
83
84 fn format_documents(&self, docs: &[Document]) -> Result<String> {
92 let json_docs: Vec<serde_json::Value> = docs
94 .iter()
95 .map(|doc| self.bson_to_simplified_json(doc))
96 .collect();
97
98 let json_str = if self.pretty {
99 self.to_pretty_string(&json_docs)
100 .unwrap_or_else(|_| format!("{:?}", docs))
101 } else {
102 serde_json::to_string(&json_docs).unwrap_or_else(|_| format!("{:?}", docs))
103 };
104
105 if self.use_colors && self.pretty {
108 Ok(json_str.to_colored_json_auto().unwrap_or(json_str))
109 } else {
110 Ok(json_str)
111 }
112 }
113
114 pub fn format_document(&self, doc: &Document) -> Result<String> {
122 let json_value = self.bson_to_simplified_json(doc);
124
125 let json_str = if self.pretty {
126 self.to_pretty_string(&json_value)
127 .unwrap_or_else(|_| format!("{:?}", doc))
128 } else {
129 serde_json::to_string(&json_value).unwrap_or_else(|_| format!("{:?}", doc))
130 };
131
132 if self.use_colors && self.pretty {
135 Ok(json_str.to_colored_json_auto().unwrap_or(json_str))
136 } else {
137 Ok(json_str)
138 }
139 }
140
141 fn to_pretty_string<T: serde::Serialize>(
149 &self,
150 value: &T,
151 ) -> std::result::Result<String, serde_json::Error> {
152 let mut buf = Vec::new();
153 let indent = " ".repeat(self.indent);
154 let formatter = serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes());
155 let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
156 value.serialize(&mut ser)?;
157 Ok(String::from_utf8(buf).unwrap())
158 }
159
160 fn bson_to_simplified_json(&self, doc: &Document) -> serde_json::Value {
169 let mut map = serde_json::Map::new();
170
171 for (key, value) in doc {
172 map.insert(key.clone(), self.bson_value_to_json(value));
173 }
174
175 serde_json::Value::Object(map)
176 }
177
178 fn bson_value_to_json(&self, value: &Bson) -> serde_json::Value {
180 use serde_json::Value as JsonValue;
181
182 match value {
183 Bson::ObjectId(oid) => JsonValue::String(oid.to_string()),
184 Bson::DateTime(dt) => {
185 let iso = dt
186 .try_to_rfc3339_string()
187 .unwrap_or_else(|_| format!("{}", dt.timestamp_millis()));
188 JsonValue::String(iso)
189 }
190 Bson::Int64(n) => JsonValue::Number((*n).into()),
191 Bson::Int32(n) => JsonValue::Number((*n).into()),
192 Bson::Double(f) => serde_json::Number::from_f64(*f)
193 .map(JsonValue::Number)
194 .unwrap_or(JsonValue::Null),
195 Bson::Decimal128(d) => {
196 let s = d.to_string();
198 s.parse::<f64>()
199 .ok()
200 .and_then(serde_json::Number::from_f64)
201 .map(JsonValue::Number)
202 .unwrap_or_else(|| JsonValue::String(s))
203 }
204 Bson::String(s) => JsonValue::String(s.clone()),
205 Bson::Boolean(b) => JsonValue::Bool(*b),
206 Bson::Null => JsonValue::Null,
207 Bson::Array(arr) => {
208 let json_arr: Vec<JsonValue> =
209 arr.iter().map(|v| self.bson_value_to_json(v)).collect();
210 JsonValue::Array(json_arr)
211 }
212 Bson::Document(doc) => self.bson_to_simplified_json(doc),
213 Bson::Binary(bin) => {
214 use base64::Engine;
216 let base64_str = base64::engine::general_purpose::STANDARD.encode(&bin.bytes);
217 JsonValue::String(base64_str)
218 }
219 Bson::RegularExpression(regex) => {
220 JsonValue::String(format!("/{}/{}", regex.pattern, regex.options))
221 }
222 Bson::Timestamp(ts) => {
223 let millis = (ts.time as i64) * 1000 + (ts.increment as i64);
225 JsonValue::Number(millis.into())
226 }
227 _ => JsonValue::String(format!("{:?}", value)),
228 }
229 }
230}
231
232impl Default for JsonFormatter {
233 fn default() -> Self {
234 Self::new(true, false, 2)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use mongodb::bson::doc;
242
243 #[test]
244 fn test_json_formatter() {
245 let formatter = JsonFormatter::new(false, false, 2);
246 let doc = doc! { "name": "test", "value": 42 };
247 let result = formatter.format_document(&doc).unwrap();
248 assert!(result.contains("name"));
249 assert!(result.contains("test"));
250 }
251
252 #[test]
253 fn test_json_formatter_simplified_objectid() {
254 use mongodb::bson::oid::ObjectId;
255 let formatter = JsonFormatter::new(true, false, 2);
256 let oid = ObjectId::parse_str("65705d84dfc3f3b5094e1f72").unwrap();
257 let doc = doc! { "_id": oid };
258 let result = formatter.format_document(&doc).unwrap();
259 assert!(result.contains("\"_id\""));
261 assert!(result.contains("\"65705d84dfc3f3b5094e1f72\""));
262 assert!(!result.contains("$oid"));
263 }
264
265 #[test]
266 fn test_json_formatter_simplified_datetime() {
267 use mongodb::bson::DateTime;
268 let formatter = JsonFormatter::new(true, false, 2);
269 let dt = DateTime::from_millis(1701862788373);
270 let doc = doc! { "created_time": dt };
271 let result = formatter.format_document(&doc).unwrap();
272 assert!(result.contains("\"created_time\""));
274 assert!(result.contains("2023-12-06"));
275 assert!(!result.contains("$date"));
276 assert!(!result.contains("$numberLong"));
277 }
278
279 #[test]
280 fn test_json_formatter_simplified_long() {
281 let formatter = JsonFormatter::new(true, false, 2);
282 let doc = doc! { "user_id": 1i64 };
283 let result = formatter.format_document(&doc).unwrap();
284 assert!(result.contains("\"user_id\""));
286 assert!(result.contains("1"));
287 assert!(!result.contains("Long"));
288 }
289
290 #[test]
291 fn test_json_formatter_complete_document() {
292 use mongodb::bson::{DateTime, oid::ObjectId};
293 let formatter = JsonFormatter::new(true, false, 2);
294 let oid = ObjectId::parse_str("65705d84dfc3f3b5094e1f72").unwrap();
295 let dt = DateTime::from_millis(1701862788373);
296 let doc = doc! {
297 "_id": oid,
298 "user_id": 1i64,
299 "nickname": "dalei",
300 "oauth2": null,
301 "created_time": dt
302 };
303 let result = formatter.format_document(&doc).unwrap();
304
305 assert!(result.contains("\"_id\": \"65705d84dfc3f3b5094e1f72\""));
307 assert!(result.contains("\"user_id\": 1"));
308 assert!(result.contains("\"nickname\": \"dalei\""));
309 assert!(result.contains("\"oauth2\": null"));
310 assert!(result.contains("\"created_time\": \"2023-12-06"));
311
312 assert!(!result.contains("$oid"));
314 assert!(!result.contains("$date"));
315 }
316
317 #[test]
318 fn test_json_formatter_compact() {
319 let formatter = JsonFormatter::new(false, false, 2);
320 let doc = doc! { "name": "test", "value": 42 };
321 let result = formatter.format_document(&doc).unwrap();
322
323 assert!(
325 !result.contains('\n'),
326 "Compact JSON should not contain newlines"
327 );
328 assert!(
329 !result.contains(" "),
330 "Compact JSON should not contain double spaces"
331 );
332
333 assert!(result.contains("name"));
335 assert!(result.contains("test"));
336 assert!(result.contains("value"));
337 }
338
339 #[test]
340 fn test_json_formatter_compact_vs_pretty() {
341 let compact = JsonFormatter::new(false, false, 2);
342 let pretty = JsonFormatter::new(true, false, 2);
343 let doc = doc! { "a": 1, "b": 2, "c": 3 };
344
345 let compact_result = compact.format_document(&doc).unwrap();
346 let pretty_result = pretty.format_document(&doc).unwrap();
347
348 assert!(compact_result.len() < pretty_result.len());
350
351 assert!(pretty_result.contains('\n'));
353 assert!(!compact_result.contains('\n'));
354 }
355}