diff --git a/src/functions.rs b/src/functions.rs index ba7e241..884c344 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -975,33 +975,103 @@ pub fn to_string(value: &[u8]) -> String { } let mut json = String::new(); - container_to_string(value, &mut 0, &mut json); + container_to_string(value, &mut 0, &mut json, &PrettyOpts::new(false)); json } -fn container_to_string(value: &[u8], offset: &mut usize, json: &mut String) { +/// Convert `JSONB` value to pretty String +pub fn to_pretty_string(value: &[u8]) -> String { + if !is_jsonb(value) { + return String::from_utf8_lossy(value).to_string(); + } + + let mut json = String::new(); + container_to_string(value, &mut 0, &mut json, &PrettyOpts::new(true)); + json +} + +struct PrettyOpts { + enabled: bool, + indent: usize, +} + +impl PrettyOpts { + fn new(enabled: bool) -> Self { + Self { enabled, indent: 0 } + } + + fn inc_indent(&self) -> Self { + Self { + enabled: self.enabled, + indent: self.indent + 2, + } + } + + fn generate_indent(&self) -> String { + String::from_utf8(vec![0x20; self.indent]).unwrap() + } +} + +fn container_to_string( + value: &[u8], + offset: &mut usize, + json: &mut String, + pretty_opts: &PrettyOpts, +) { let header = read_u32(value, *offset).unwrap(); match header & CONTAINER_HEADER_TYPE_MASK { SCALAR_CONTAINER_TAG => { let mut jentry_offset = 4 + *offset; let mut value_offset = 8 + *offset; - scalar_to_string(value, &mut jentry_offset, &mut value_offset, json); + scalar_to_string( + value, + &mut jentry_offset, + &mut value_offset, + json, + pretty_opts, + ); } ARRAY_CONTAINER_TAG => { - json.push('['); + if pretty_opts.enabled { + json.push_str("[\n"); + } else { + json.push('['); + } let length = (header & CONTAINER_HEADER_LEN_MASK) as usize; let mut jentry_offset = 4 + *offset; let mut value_offset = 4 + *offset + 4 * length; + let inner_pretty_ops = pretty_opts.inc_indent(); for i in 0..length { if i > 0 { - json.push(','); + if pretty_opts.enabled { + json.push_str(",\n"); + } else { + json.push(','); + } } - scalar_to_string(value, &mut jentry_offset, &mut value_offset, json); + if pretty_opts.enabled { + json.push_str(&inner_pretty_ops.generate_indent()); + } + scalar_to_string( + value, + &mut jentry_offset, + &mut value_offset, + json, + &inner_pretty_ops, + ); + } + if pretty_opts.enabled { + json.push('\n'); + json.push_str(&pretty_opts.generate_indent()); } json.push(']'); } OBJECT_CONTAINER_TAG => { - json.push('{'); + if pretty_opts.enabled { + json.push_str("{\n"); + } else { + json.push('{'); + } let length = (header & CONTAINER_HEADER_LEN_MASK) as usize; let mut jentry_offset = 4 + *offset; let mut key_offset = 4 + *offset + 8 * length; @@ -1015,14 +1085,35 @@ fn container_to_string(value: &[u8], offset: &mut usize, json: &mut String) { key_offset += key_length; } let mut value_offset = key_offset; + let inner_pretty_ops = pretty_opts.inc_indent(); for i in 0..length { if i > 0 { - json.push(','); + if pretty_opts.enabled { + json.push_str(",\n"); + } else { + json.push(','); + } } let (key_start, key_end) = keys.pop_front().unwrap(); - escape_scalar_string(value, key_start, key_end, json); - json.push(':'); - scalar_to_string(value, &mut jentry_offset, &mut value_offset, json); + if pretty_opts.enabled { + json.push_str(&inner_pretty_ops.generate_indent()); + escape_scalar_string(value, key_start, key_end, json); + json.push_str(": "); + } else { + escape_scalar_string(value, key_start, key_end, json); + json.push(':'); + } + scalar_to_string( + value, + &mut jentry_offset, + &mut value_offset, + json, + &inner_pretty_ops, + ); + } + if pretty_opts.enabled { + json.push('\n'); + json.push_str(&pretty_opts.generate_indent()); } json.push('}'); } @@ -1035,6 +1126,7 @@ fn scalar_to_string( jentry_offset: &mut usize, value_offset: &mut usize, json: &mut String, + pretty_opts: &PrettyOpts, ) { let jentry_encoded = read_u32(value, *jentry_offset).unwrap(); let jentry = JEntry::decode_jentry(jentry_encoded); @@ -1051,7 +1143,7 @@ fn scalar_to_string( escape_scalar_string(value, *value_offset, *value_offset + length, json); } CONTAINER_TAG => { - container_to_string(value, value_offset, json); + container_to_string(value, value_offset, json, pretty_opts); } _ => {} } diff --git a/tests/it/functions.rs b/tests/it/functions.rs index 5d7aeea..d7df626 100644 --- a/tests/it/functions.rs +++ b/tests/it/functions.rs @@ -18,8 +18,8 @@ use std::cmp::Ordering; use jsonb::{ array_length, array_values, as_bool, as_null, as_number, as_str, build_array, build_object, compare, convert_to_comparable, from_slice, get_by_index, get_by_name, get_by_path, is_array, - is_object, object_keys, parse_value, to_bool, to_f64, to_i64, to_str, to_string, to_u64, - traverse_check_string, Number, Object, Value, + is_object, object_keys, parse_value, to_bool, to_f64, to_i64, to_pretty_string, to_str, + to_string, to_u64, traverse_check_string, Number, Object, Value, }; use jsonb::jsonpath::parse_json_path; @@ -782,6 +782,72 @@ fn test_to_string() { } } +#[test] +fn test_to_pretty_string() { + let sources = vec![ + (r#"null"#, r#"null"#), + (r#"true"#, r#"true"#), + (r#"false"#, r#"false"#), + (r#"1234567"#, r#"1234567"#), + (r#"-1234567"#, r#"-1234567"#), + (r#"123.4567"#, r#"123.4567"#), + (r#""abcdef""#, r#""abcdef""#), + (r#""ab\n\"\uD83D\uDC8Eζ΅‹θ―•""#, r#""ab\n\"πŸ’Žζ΅‹θ―•""#), + (r#""α€™α€Όα€”α€Ία€™α€¬α€˜α€¬α€žα€¬""#, r#""α€™α€Όα€”α€Ία€™α€¬α€˜α€¬α€žα€¬""#), + (r#""βš οΈβœ…βŒ""#, r#""βš οΈβœ…βŒ""#), + (r#"[1,2,3,4]"#, "[\n 1,\n 2,\n 3,\n 4\n]"), + ( + r#"[1,2,3,4]"#, + r#"[ + 1, + 2, + 3, + 4 +]"#, + ), + ( + r#"["a","b",true,false,[1,2,3],{"a":"b"}]"#, + r#"[ + "a", + "b", + true, + false, + [ + 1, + 2, + 3 + ], + { + "a": "b" + } +]"#, + ), + ( + r#"{"k1":"v1","k2":[1,2,3],"k3":{"a":"b"}}"#, + r#"{ + "k1": "v1", + "k2": [ + 1, + 2, + 3 + ], + "k3": { + "a": "b" + } +}"#, + ), + ]; + + let mut buf: Vec = Vec::new(); + for (s, expect) in sources { + let value = parse_value(s.as_bytes()).unwrap(); + value.write_to_vec(&mut buf); + let res = to_pretty_string(&buf); + assert_eq!(res, expect); + buf.clear(); + } +} + #[test] fn test_traverse_check_string() { let sources = vec![