1 parent
a0de029a23
commit
dc2d3ea1dc
14 files changed
+136
-12
No files matched your search
@@ -112,11 +112,12 @@ impl Default for DataGridSqlExtractorOptions {
|
||||
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
|
||||
pub struct DataGridJsonExtractorOptions {
|
||||
pub pretty: bool,
|
||||
pub camel_case_field_names: bool,
|
||||
}
|
||||
|
||||
impl Default for DataGridJsonExtractorOptions {
|
||||
fn default() -> Self {
|
||||
Self { pretty: DEFAULT_JSON_PRETTY }
|
||||
Self { pretty: DEFAULT_JSON_PRETTY, camel_case_field_names: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ pub(super) fn write_json(
|
||||
context: &ExtractContext<'_>,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<Vec<DataGridExtractWarning>, DataGridExtractError> {
|
||||
let (names, warnings) = unique_json_names(&context.selected_columns);
|
||||
let (names, warnings) =
|
||||
unique_json_names(&context.selected_columns, context.request.options.json.camel_case_field_names);
|
||||
let rows =
|
||||
JsonRows { names: &names, source_indexes: &context.selected_source_indexes, rows: &context.request.rows };
|
||||
let result = if context.request.options.json.pretty {
|
||||
@@ -31,7 +32,8 @@ pub(super) fn write_json_lines(
|
||||
context: &ExtractContext<'_>,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<Vec<DataGridExtractWarning>, DataGridExtractError> {
|
||||
let (names, warnings) = unique_json_names(&context.selected_columns);
|
||||
let (names, warnings) =
|
||||
unique_json_names(&context.selected_columns, context.request.options.json.camel_case_field_names);
|
||||
for (index, row) in context.request.rows.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write_bytes(output, b"\n")?;
|
||||
@@ -92,15 +94,28 @@ impl Serialize for JsonRow<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn unique_json_names(columns: &[&DataGridExtractColumn]) -> (Vec<String>, Vec<DataGridExtractWarning>) {
|
||||
let reserved = columns.iter().map(|column| column.display_name.as_str()).collect::<std::collections::HashSet<_>>();
|
||||
fn unique_json_names(
|
||||
columns: &[&DataGridExtractColumn],
|
||||
camel_case_field_names: bool,
|
||||
) -> (Vec<String>, Vec<DataGridExtractWarning>) {
|
||||
let base_names = columns
|
||||
.iter()
|
||||
.map(|column| {
|
||||
if camel_case_field_names {
|
||||
lower_camel_json_name(&column.display_name)
|
||||
} else {
|
||||
column.display_name.clone()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let reserved = base_names.iter().map(String::as_str).collect::<std::collections::HashSet<_>>();
|
||||
let mut emitted = std::collections::HashSet::<String>::new();
|
||||
let mut suffixes = std::collections::HashMap::<String, usize>::new();
|
||||
let mut duplicate = false;
|
||||
let names = columns
|
||||
let names = base_names
|
||||
.iter()
|
||||
.map(|column| {
|
||||
let base = column.display_name.clone();
|
||||
.map(|base| {
|
||||
let base = base.clone();
|
||||
if emitted.insert(base.clone()) {
|
||||
return base;
|
||||
}
|
||||
@@ -125,3 +140,23 @@ fn unique_json_names(columns: &[&DataGridExtractColumn]) -> (Vec<String>, Vec<Da
|
||||
};
|
||||
(names, warnings)
|
||||
}
|
||||
|
||||
fn lower_camel_json_name(name: &str) -> String {
|
||||
if !name.contains('_') {
|
||||
return name.to_owned();
|
||||
}
|
||||
|
||||
let mut words = name.split('_').filter(|word| !word.is_empty());
|
||||
let Some(first) = words.next() else {
|
||||
return name.to_owned();
|
||||
};
|
||||
let mut converted = first.to_lowercase();
|
||||
for word in words {
|
||||
let mut characters = word.chars();
|
||||
if let Some(first_character) = characters.next() {
|
||||
converted.extend(first_character.to_uppercase());
|
||||
converted.extend(characters.flat_map(char::to_lowercase));
|
||||
}
|
||||
}
|
||||
converted
|
||||
}
|
||||
@@ -43,6 +43,7 @@ fn partially_deserialized_options_use_the_canonical_defaults() {
|
||||
assert_eq!(options.sql.insert_mode, crate::data_grid_sql::DataGridCopyInsertMode::Merged);
|
||||
assert!(!options.sql.exclude_primary_keys_from_insert);
|
||||
assert!(options.json.pretty);
|
||||
assert!(!options.json.camel_case_field_names);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -350,6 +351,56 @@ fn extracts_compact_json_when_pretty_printing_is_disabled() {
|
||||
assert_eq!(result.text, "[{\"id\":1,\"name\":\"Ada\"},{\"id\":2,\"name\":\"Grace, Hopper\"}]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_snake_case_json_field_names_only_when_enabled() {
|
||||
let mut request = request(DataGridExtractorId::Json);
|
||||
request.columns = vec![
|
||||
column("user_id", 0),
|
||||
column("ORDER_ITEM_ID", 1),
|
||||
column("alreadyCamel", 2),
|
||||
column("_display__name_", 3),
|
||||
column("naïve_value", 4),
|
||||
];
|
||||
request.selected_column_indexes = vec![0, 1, 2, 3, 4];
|
||||
request.rows = vec![vec![json!(7), json!(9), json!("kept"), json!("Ada"), json!(true)]];
|
||||
|
||||
let unchanged = extract_data_grid_selection(request.clone()).expect("default JSON extraction");
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Value>(&unchanged.text).expect("valid default JSON"),
|
||||
json!([{"user_id": 7, "ORDER_ITEM_ID": 9, "alreadyCamel": "kept", "_display__name_": "Ada", "naïve_value": true}])
|
||||
);
|
||||
|
||||
request.options.json.camel_case_field_names = true;
|
||||
let converted = extract_data_grid_selection(request).expect("camel-case JSON extraction");
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Value>(&converted.text).expect("valid converted JSON"),
|
||||
json!([{"userId": 7, "orderItemId": 9, "alreadyCamel": "kept", "displayName": "Ada", "naïveValue": true}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_json_lines_names_and_suffixes_post_conversion_collisions() {
|
||||
let mut request = request(DataGridExtractorId::JsonLines);
|
||||
request.columns = vec![column("user_id", 0), column("userId", 1), column("___", 2)];
|
||||
request.selected_column_indexes = vec![0, 1, 2];
|
||||
request.rows = vec![vec![json!(1), json!(2), json!(3)], vec![json!(4), json!(5), json!(6)]];
|
||||
request.options.json.camel_case_field_names = true;
|
||||
|
||||
let result = extract_data_grid_selection(request).expect("camel-case JSON Lines extraction");
|
||||
let rows = result
|
||||
.text
|
||||
.lines()
|
||||
.map(|line| serde_json::from_str::<Value>(line).expect("valid JSON Lines record"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
rows,
|
||||
vec![json!({"userId": 1, "userId_2": 2, "___": 3}), json!({"userId": 4, "userId_2": 5, "___": 6})]
|
||||
);
|
||||
assert_eq!(result.warnings.len(), 1);
|
||||
assert_eq!(result.warnings[0].code, DataGridExtractWarningCode::DuplicateJsonColumnNames);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_null_safe_where_clause_predicates() {
|
||||
let mut request = request(DataGridExtractorId::WhereClause);
|
||||
@@ -704,6 +755,7 @@ fn extractor_contract_uses_the_frontend_camel_case_wire_shape() {
|
||||
assert_eq!(value["selectedColumnIndexes"], json!([0, 1]));
|
||||
assert_eq!(value["selectionKind"], "cells");
|
||||
assert_eq!(value["options"]["dsv"]["includeColumnHeader"], false);
|
||||
assert_eq!(value["options"]["json"]["camelCaseFieldNames"], false);
|
||||
assert!(value.get("selected_column_indexes").is_none());
|
||||
|
||||
let decoded = serde_json::from_value::<DataGridExtractRequest>(value).expect("deserialize extractor request");
|
||||
|
||||
Reference in new issue
Block a user