use smallvec::SmallVec;
use std::sync::LazyLock;
pub(crate) fn parse_env_usize(name: &str, default: usize) -> usize {
std::env::var(name)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
pub(crate) fn parse_env_usize_opt(name: &str) -> Option<usize> {
std::env::var(name).ok().and_then(|v| v.parse().ok())
}
pub(crate) static DEFAULT_PARALLEL_THRESHOLD: LazyLock<usize> =
LazyLock::new(|| parse_env_usize("JSON_TOOLS_PARALLEL_THRESHOLD", 100));
pub(crate) static DEFAULT_NESTED_PARALLEL_THRESHOLD: LazyLock<usize> =
LazyLock::new(|| parse_env_usize("JSON_TOOLS_NESTED_PARALLEL_THRESHOLD", 100));
pub(crate) static DEFAULT_MAX_ARRAY_INDEX: LazyLock<usize> =
LazyLock::new(|| parse_env_usize("JSON_TOOLS_MAX_ARRAY_INDEX", 100_000));
pub(crate) static DEFAULT_NUM_THREADS: LazyLock<Option<usize>> =
LazyLock::new(|| parse_env_usize_opt("JSON_TOOLS_NUM_THREADS"));
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)] pub(crate) enum OperationMode {
Flatten,
Unflatten,
Normal,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct FilteringConfig {
pub remove_empty_strings: bool,
pub remove_nulls: bool,
pub remove_empty_objects: bool,
pub remove_empty_arrays: bool,
}
impl FilteringConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn remove_empty_strings(mut self, enabled: bool) -> Self {
self.remove_empty_strings = enabled;
self
}
#[must_use]
pub fn remove_nulls(mut self, enabled: bool) -> Self {
self.remove_nulls = enabled;
self
}
#[must_use]
pub fn remove_empty_objects(mut self, enabled: bool) -> Self {
self.remove_empty_objects = enabled;
self
}
#[must_use]
pub fn remove_empty_arrays(mut self, enabled: bool) -> Self {
self.remove_empty_arrays = enabled;
self
}
pub fn has_any_filter(&self) -> bool {
self.remove_empty_strings
|| self.remove_nulls
|| self.remove_empty_objects
|| self.remove_empty_arrays
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct CollisionConfig {
pub handle_collisions: bool,
}
impl CollisionConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn handle_collisions(mut self, enabled: bool) -> Self {
self.handle_collisions = enabled;
self
}
pub fn has_collision_handling(&self) -> bool {
self.handle_collisions
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct ReplacementConfig {
pub key_replacements: SmallVec<[(String, String); 2]>,
pub value_replacements: SmallVec<[(String, String); 2]>,
pub key_exclusions: SmallVec<[String; 2]>,
pub value_exclusions: SmallVec<[String; 2]>,
}
impl ReplacementConfig {
pub fn new() -> Self {
Self {
key_replacements: SmallVec::new(),
value_replacements: SmallVec::new(),
key_exclusions: SmallVec::new(),
value_exclusions: SmallVec::new(),
}
}
#[must_use]
pub fn add_key_replacement(
mut self,
find: impl Into<String>,
replace: impl Into<String>,
) -> Self {
self.key_replacements.push((find.into(), replace.into()));
self
}
#[must_use]
pub fn add_value_replacement(
mut self,
find: impl Into<String>,
replace: impl Into<String>,
) -> Self {
self.value_replacements.push((find.into(), replace.into()));
self
}
pub fn has_key_replacements(&self) -> bool {
!self.key_replacements.is_empty()
}
pub fn has_value_replacements(&self) -> bool {
!self.value_replacements.is_empty()
}
#[must_use]
pub fn add_key_exclusion(mut self, pattern: impl Into<String>) -> Self {
self.key_exclusions.push(pattern.into());
self
}
pub fn has_key_exclusions(&self) -> bool {
!self.key_exclusions.is_empty()
}
#[must_use]
pub fn add_value_exclusion(mut self, pattern: impl Into<String>) -> Self {
self.value_exclusions.push(pattern.into());
self
}
pub fn has_value_exclusions(&self) -> bool {
!self.value_exclusions.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct DateConversionConfig {
pub enabled: bool,
pub normalize_to_utc: bool,
pub assume_utc_for_naive: bool,
}
impl Default for DateConversionConfig {
fn default() -> Self {
Self {
enabled: false,
normalize_to_utc: true,
assume_utc_for_naive: true,
}
}
}
impl DateConversionConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[must_use]
pub fn normalize_to_utc(mut self, enabled: bool) -> Self {
self.normalize_to_utc = enabled;
self
}
#[must_use]
pub fn assume_utc_for_naive(mut self, enabled: bool) -> Self {
self.assume_utc_for_naive = enabled;
self
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct NullConversionConfig {
pub enabled: bool,
pub extra_tokens: SmallVec<[String; 2]>,
}
impl NullConversionConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[must_use]
pub fn add_extra_token(mut self, token: impl Into<String>) -> Self {
self.extra_tokens.push(token.into());
self
}
pub fn has_extra_tokens(&self) -> bool {
!self.extra_tokens.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct BooleanConversionConfig {
pub enabled: bool,
pub extra_true_tokens: SmallVec<[String; 2]>,
pub extra_false_tokens: SmallVec<[String; 2]>,
}
impl BooleanConversionConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[must_use]
pub fn add_extra_true_token(mut self, token: impl Into<String>) -> Self {
self.extra_true_tokens.push(token.into());
self
}
#[must_use]
pub fn add_extra_false_token(mut self, token: impl Into<String>) -> Self {
self.extra_false_tokens.push(token.into());
self
}
pub fn has_extra_tokens(&self) -> bool {
!self.extra_true_tokens.is_empty() || !self.extra_false_tokens.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct NumberConversionConfig {
pub enabled: bool,
pub currency: bool,
pub percent: bool,
pub basis_points: bool,
pub suffixes: bool,
pub fractions: bool,
pub radix: bool,
}
impl Default for NumberConversionConfig {
fn default() -> Self {
Self {
enabled: false,
currency: true,
percent: true,
basis_points: true,
suffixes: true,
fractions: true,
radix: true,
}
}
}
impl NumberConversionConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[must_use]
pub fn currency(mut self, enabled: bool) -> Self {
self.currency = enabled;
self
}
#[must_use]
pub fn percent(mut self, enabled: bool) -> Self {
self.percent = enabled;
self
}
#[must_use]
pub fn basis_points(mut self, enabled: bool) -> Self {
self.basis_points = enabled;
self
}
#[must_use]
pub fn suffixes(mut self, enabled: bool) -> Self {
self.suffixes = enabled;
self
}
#[must_use]
pub fn fractions(mut self, enabled: bool) -> Self {
self.fractions = enabled;
self
}
#[must_use]
pub fn radix(mut self, enabled: bool) -> Self {
self.radix = enabled;
self
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct TypeConversionConfig {
pub dates: DateConversionConfig,
pub nulls: NullConversionConfig,
pub booleans: BooleanConversionConfig,
pub numbers: NumberConversionConfig,
}
impl TypeConversionConfig {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn dates(mut self, config: DateConversionConfig) -> Self {
self.dates = config;
self
}
#[must_use]
pub fn nulls(mut self, config: NullConversionConfig) -> Self {
self.nulls = config;
self
}
#[must_use]
pub fn booleans(mut self, config: BooleanConversionConfig) -> Self {
self.booleans = config;
self
}
#[must_use]
pub fn numbers(mut self, config: NumberConversionConfig) -> Self {
self.numbers = config;
self
}
pub fn has_any_enabled(&self) -> bool {
self.dates.enabled || self.nulls.enabled || self.booleans.enabled || self.numbers.enabled
}
pub(crate) fn classify(&self) -> TypeConversionMode {
if !self.has_any_enabled() {
return TypeConversionMode::Disabled;
}
let all_default = self.dates
== DateConversionConfig {
enabled: true,
..DateConversionConfig::default()
}
&& self.nulls
== NullConversionConfig {
enabled: true,
..NullConversionConfig::default()
}
&& self.booleans
== BooleanConversionConfig {
enabled: true,
..BooleanConversionConfig::default()
}
&& self.numbers
== NumberConversionConfig {
enabled: true,
..NumberConversionConfig::default()
};
if all_default {
TypeConversionMode::AllDefault
} else {
TypeConversionMode::Custom
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum TypeConversionMode {
Disabled,
AllDefault,
Custom,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ProcessingConfig {
pub separator: String,
pub lowercase_keys: bool,
pub filtering: FilteringConfig,
pub collision: CollisionConfig,
pub replacements: ReplacementConfig,
pub type_conversion: TypeConversionConfig,
pub(crate) type_conversion_mode: TypeConversionMode,
pub parallel_threshold: usize,
pub num_threads: Option<usize>,
pub nested_parallel_threshold: usize,
pub max_array_index: usize,
}
impl Default for ProcessingConfig {
fn default() -> Self {
Self {
separator: ".".to_string(),
lowercase_keys: false,
filtering: FilteringConfig::default(),
collision: CollisionConfig::default(),
replacements: ReplacementConfig::default(),
type_conversion: TypeConversionConfig::default(),
type_conversion_mode: TypeConversionMode::Disabled,
parallel_threshold: *DEFAULT_PARALLEL_THRESHOLD,
num_threads: None, nested_parallel_threshold: *DEFAULT_NESTED_PARALLEL_THRESHOLD,
max_array_index: *DEFAULT_MAX_ARRAY_INDEX,
}
}
}
impl ProcessingConfig {
pub fn new() -> Self {
Self::default()
}
pub(crate) fn effective_thread_count(&self, item_count: usize) -> usize {
let base = self.num_threads.unwrap_or_else(|| {
std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(4)
});
base.max(1).min(item_count)
}
#[must_use]
pub fn separator(mut self, separator: impl Into<String>) -> Self {
self.separator = separator.into();
self
}
#[must_use]
pub fn lowercase_keys(mut self, enabled: bool) -> Self {
self.lowercase_keys = enabled;
self
}
#[must_use]
pub fn filtering(mut self, filtering: FilteringConfig) -> Self {
self.filtering = filtering;
self
}
#[must_use]
pub fn collision(mut self, collision: CollisionConfig) -> Self {
self.collision = collision;
self
}
#[must_use]
pub fn replacements(mut self, replacements: ReplacementConfig) -> Self {
self.replacements = replacements;
self
}
#[must_use]
pub fn type_conversion(mut self, type_conversion: TypeConversionConfig) -> Self {
self.type_conversion_mode = type_conversion.classify();
self.type_conversion = type_conversion;
self
}
}