use crate::fxhash::FxHashMap;
use compact_str::CompactString;
use memchr::memchr;
use rayon::prelude::*;
use smallvec::SmallVec;
use std::borrow::Cow;
use crate::cache::{get_cached_regex, parse_pattern, ParsedPattern};
use crate::config::ProcessingConfig;
use crate::convert::try_convert_string_to_json_bytes;
use crate::error::JsonToolsError;
use crate::json_parser;
use crate::transform::{apply_key_replacement_patterns, apply_value_replacement_patterns};
#[derive(Clone)]
pub(crate) struct SeparatorCache {
pub(crate) separator: Cow<'static, str>,
is_single_char: bool,
single_char: Option<char>,
}
impl SeparatorCache {
#[inline]
pub(crate) fn new(separator: &str) -> Self {
let separator_cow = match separator {
"." => Cow::Borrowed("."),
"_" => Cow::Borrowed("_"),
"::" => Cow::Borrowed("::"),
"/" => Cow::Borrowed("/"),
"-" => Cow::Borrowed("-"),
"|" => Cow::Borrowed("|"),
"->" => Cow::Borrowed("->"),
"__" => Cow::Borrowed("__"),
"#" => Cow::Borrowed("#"),
"~" => Cow::Borrowed("~"),
"@" => Cow::Borrowed("@"),
"%" => Cow::Borrowed("%"),
_ => Cow::Owned(separator.to_string()),
};
let is_single_char = separator.len() == 1;
let single_char = if is_single_char {
separator.chars().next()
} else {
None
};
Self {
separator: separator_cow,
is_single_char,
single_char,
}
}
#[inline]
pub(crate) fn append_to_buffer(&self, buffer: &mut String) {
if self.is_single_char {
buffer.push(
self.single_char
.expect("SeparatorCache: single_char set for single-byte separator"),
);
} else {
buffer.push_str(&self.separator);
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum EntryKind {
ObjectStart = 0, ObjectEnd = 1, ArrayStart = 2, ArrayEnd = 3, StringStart = 4, Colon = 5,
Comma = 6,
ScalarStart = 7, }
pub(crate) const STRING_HAS_ESCAPE_BIT: u32 = 1 << 23;
pub(crate) const STRING_LENGTH_MASK: u32 = (1 << 23) - 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub(crate) struct TapeEntry {
pub(crate) offset: u32,
pub(crate) tag: u32,
}
impl TapeEntry {
#[inline(always)]
pub(crate) fn new(offset: usize, kind: EntryKind, aux: u32) -> Self {
debug_assert!(offset <= u32::MAX as usize);
debug_assert!(aux <= 0x00FF_FFFF);
Self {
offset: offset as u32,
tag: (kind as u32) | (aux << 8),
}
}
#[inline(always)]
pub(crate) fn kind(self) -> EntryKind {
unsafe { std::mem::transmute(self.tag as u8) }
}
#[inline(always)]
pub(crate) fn aux(self) -> u32 {
self.tag >> 8
}
#[inline(always)]
pub(crate) fn offset(self) -> usize {
self.offset as usize
}
#[inline(always)]
pub(crate) fn set_aux(&mut self, aux: u32) {
debug_assert!(aux <= 0x00FF_FFFF);
self.tag = (self.tag & 0xFF) | (aux << 8);
}
#[inline(always)]
pub(crate) fn string_content_len(self) -> usize {
(self.aux() & STRING_LENGTH_MASK) as usize
}
#[inline(always)]
pub(crate) fn string_has_escapes(self) -> bool {
(self.aux() & STRING_HAS_ESCAPE_BIT) != 0
}
}
#[inline(always)]
pub(crate) fn tape_is_empty_object(tape: &[TapeEntry], idx: usize) -> bool {
idx + 1 < tape.len() && tape[idx + 1].kind() == EntryKind::ObjectEnd
}
#[inline(always)]
pub(crate) fn tape_is_empty_array(tape: &[TapeEntry], idx: usize) -> bool {
idx + 1 < tape.len() && tape[idx + 1].kind() == EntryKind::ArrayEnd
}
#[inline(always)]
pub(crate) fn tape_entry(tape: &[TapeEntry], idx: usize) -> TapeEntry {
debug_assert!(
idx < tape.len(),
"tape index {idx} out of bounds (len {})",
tape.len()
);
unsafe { *tape.get_unchecked(idx) }
}
#[inline(always)]
pub(crate) fn tape_content_bytes(input: &[u8], entry: TapeEntry) -> &[u8] {
let start = entry.offset() + 1;
let len = entry.string_content_len();
debug_assert!(start + len <= input.len());
unsafe { input.get_unchecked(start..start + len) }
}
#[inline(always)]
pub(crate) fn tape_content_str(input: &[u8], entry: TapeEntry) -> &str {
unsafe { std::str::from_utf8_unchecked(tape_content_bytes(input, entry)) }
}
#[inline(always)]
pub(crate) fn tape_quoted_str(input: &[u8], entry: TapeEntry) -> &str {
let str_offset = entry.offset();
let content_len = entry.string_content_len();
let end = str_offset + 1 + content_len + 1; debug_assert!(end <= input.len());
unsafe { std::str::from_utf8_unchecked(input.get_unchecked(str_offset..end)) }
}
#[inline(always)]
pub(crate) fn tape_scalar_bytes(input: &[u8], entry: TapeEntry) -> &[u8] {
let start = entry.offset();
let len = entry.aux() as usize;
debug_assert!(start + len <= input.len());
unsafe { input.get_unchecked(start..start + len) }
}
#[derive(Debug)]
pub(crate) enum ValueRef<'a> {
Raw(&'a [u8]),
Owned(String),
}
impl<'a> ValueRef<'a> {
#[inline]
pub(crate) fn as_str(&self) -> &str {
match self {
ValueRef::Raw(bytes) => unsafe { std::str::from_utf8_unchecked(bytes) },
ValueRef::Owned(s) => s.as_str(),
}
}
}
struct CollectedEntry<'a> {
key: CompactString,
value: ValueRef<'a>,
}
const CLASS_NONE: u8 = 0;
const CLASS_LBRACE: u8 = 1; const CLASS_RBRACE: u8 = 2; const CLASS_LBRACKET: u8 = 3; const CLASS_RBRACKET: u8 = 4; const CLASS_QUOTE: u8 = 5; const CLASS_COLON: u8 = 6; const CLASS_COMMA: u8 = 7;
const fn build_byte_lut() -> [u8; 256] {
let mut lut = [CLASS_NONE; 256];
lut[b'{' as usize] = CLASS_LBRACE;
lut[b'}' as usize] = CLASS_RBRACE;
lut[b'[' as usize] = CLASS_LBRACKET;
lut[b']' as usize] = CLASS_RBRACKET;
lut[b'"' as usize] = CLASS_QUOTE;
lut[b':' as usize] = CLASS_COLON;
lut[b',' as usize] = CLASS_COMMA;
lut
}
static BYTE_LUT: [u8; 256] = build_byte_lut();
pub(crate) fn scan_and_fixup(input: &[u8]) -> Result<Vec<TapeEntry>, JsonToolsError> {
let mut tape = Vec::with_capacity(input.len() / 3);
let mut stack: SmallVec<[u32; 32]> = SmallVec::new();
let len = input.len();
let mut pos: usize = 0;
while pos < len {
let b = unsafe { *input.get_unchecked(pos) };
let class = unsafe { *BYTE_LUT.get_unchecked(b as usize) };
if class == CLASS_NONE {
pos += 1;
continue;
}
match class {
CLASS_QUOTE => {
let str_start = pos;
pos += 1; let content_start = pos;
loop {
match memchr(b'"', unsafe { input.get_unchecked(pos..len) }) {
Some(offset) => {
let candidate = pos + offset;
let bs = count_trailing_backslashes_fast(input, candidate);
if bs & 1 == 0 {
let content_len = candidate - content_start;
let has_escape = memchr(b'\\', unsafe {
input.get_unchecked(content_start..candidate)
})
.is_some();
let mut aux = content_len as u32;
if has_escape {
aux |= STRING_HAS_ESCAPE_BIT;
}
tape.push(TapeEntry::new(str_start, EntryKind::StringStart, aux));
pos = candidate + 1; break;
}
pos = candidate + 1;
}
None => {
return Err(JsonToolsError::invalid_json_structure(
"Unterminated string in JSON input",
));
}
}
}
}
CLASS_LBRACE => {
let idx = tape.len() as u32;
tape.push(TapeEntry::new(pos, EntryKind::ObjectStart, 0));
stack.push(idx);
pos += 1;
}
CLASS_RBRACE => {
let close_idx = tape.len();
tape.push(TapeEntry::new(pos, EntryKind::ObjectEnd, 0));
match stack.pop() {
Some(open_idx) => {
let open = open_idx as usize;
tape[open].set_aux(close_idx as u32);
tape[close_idx].set_aux(open_idx);
}
None => {
return Err(JsonToolsError::invalid_json_structure(
"Unexpected closing brace in JSON input",
));
}
}
pos += 1;
}
CLASS_LBRACKET => {
let idx = tape.len() as u32;
tape.push(TapeEntry::new(pos, EntryKind::ArrayStart, 0));
stack.push(idx);
pos += 1;
maybe_emit_scalar(input, len, &mut tape, &mut pos)?;
}
CLASS_RBRACKET => {
let close_idx = tape.len();
tape.push(TapeEntry::new(pos, EntryKind::ArrayEnd, 0));
match stack.pop() {
Some(open_idx) => {
let open = open_idx as usize;
tape[open].set_aux(close_idx as u32);
tape[close_idx].set_aux(open_idx);
}
None => {
return Err(JsonToolsError::invalid_json_structure(
"Unexpected closing bracket in JSON input",
));
}
}
pos += 1;
}
CLASS_COLON => {
tape.push(TapeEntry::new(pos, EntryKind::Colon, 0));
pos += 1;
maybe_emit_scalar(input, len, &mut tape, &mut pos)?;
}
CLASS_COMMA => {
tape.push(TapeEntry::new(pos, EntryKind::Comma, 0));
pos += 1;
maybe_emit_scalar(input, len, &mut tape, &mut pos)?;
}
_ => {
pos += 1;
}
}
}
if !stack.is_empty() {
return Err(JsonToolsError::invalid_json_structure(
"Unclosed object or array in JSON input",
));
}
Ok(tape)
}
#[inline(always)]
fn maybe_emit_scalar(
input: &[u8],
len: usize,
tape: &mut Vec<TapeEntry>,
pos: &mut usize,
) -> Result<(), JsonToolsError> {
let mut p = *pos;
while p < len {
let b = unsafe { *input.get_unchecked(p) };
if b > 0x20 || (b != b' ' && b != b'\t' && b != b'\n' && b != b'\r') {
break;
}
p += 1;
}
if p < len {
let next = unsafe { *input.get_unchecked(p) };
if next != b'"' && next != b'{' && next != b'[' && next != b']' && next != b'}' {
let scalar_start = p;
p += 1;
while p < len {
match unsafe { *input.get_unchecked(p) } {
b',' | b'}' | b']' | b' ' | b'\t' | b'\n' | b'\r' => break,
_ => p += 1,
}
}
let scalar_len = p - scalar_start;
let scalar_bytes = &input[scalar_start..scalar_start + scalar_len];
validate_json_scalar(scalar_bytes)?;
tape.push(TapeEntry::new(
scalar_start,
EntryKind::ScalarStart,
scalar_len as u32,
));
}
}
Ok(())
}
#[inline(always)]
fn validate_json_scalar(bytes: &[u8]) -> Result<(), JsonToolsError> {
if bytes.is_empty() {
return Err(JsonToolsError::invalid_json_structure("Empty scalar value"));
}
match bytes[0] {
b'0'..=b'9' | b'-' => Ok(()),
b't' if bytes == b"true" => Ok(()),
b'f' if bytes == b"false" => Ok(()),
b'n' if bytes == b"null" => Ok(()),
_ => {
let value = String::from_utf8_lossy(bytes);
Err(JsonToolsError::invalid_json_structure(format!(
"Invalid JSON value: `{value}`"
)))
}
}
}
#[inline(always)]
fn count_trailing_backslashes_fast(input: &[u8], pos: usize) -> usize {
let mut count = 0;
let mut p = pos;
while p > 0 {
p -= 1;
if unsafe { *input.get_unchecked(p) } != b'\\' {
break;
}
count += 1;
}
count
}
struct IntBuf {
bytes: [u8; 20], }
impl IntBuf {
#[inline]
fn new() -> Self {
Self { bytes: [0u8; 20] }
}
#[inline]
fn format(&mut self, mut n: usize) -> &str {
if n == 0 {
return "0";
}
let mut pos = 20usize;
while n > 0 {
pos -= 1;
self.bytes[pos] = b'0' + (n % 10) as u8;
n /= 10;
}
unsafe { std::str::from_utf8_unchecked(&self.bytes[pos..]) }
}
}
struct FastStreamingPathBuilder {
buffer: String,
stack: SmallVec<[usize; 16]>,
itoa_buf: IntBuf,
}
impl FastStreamingPathBuilder {
#[inline]
fn new() -> Self {
Self {
buffer: String::with_capacity(256),
stack: SmallVec::new(),
itoa_buf: IntBuf::new(),
}
}
#[inline(always)]
fn push_level(&mut self) {
self.stack.push(self.buffer.len());
}
#[inline(always)]
fn pop_level(&mut self) {
if let Some(len) = self.stack.pop() {
self.buffer.truncate(len);
}
}
#[inline(always)]
fn append_key_raw(&mut self, key: &str, separator: &SeparatorCache) {
if !self.buffer.is_empty() {
separator.append_to_buffer(&mut self.buffer);
}
self.buffer.push_str(key);
}
#[inline(always)]
fn append_index(&mut self, index: usize, separator: &SeparatorCache) {
if !self.buffer.is_empty() {
separator.append_to_buffer(&mut self.buffer);
}
self.buffer.push_str(self.itoa_buf.format(index));
}
#[inline(always)]
fn as_str(&self) -> &str {
&self.buffer
}
}
struct DirectWalker<'a> {
input: &'a [u8],
tape: &'a [TapeEntry],
path: FastStreamingPathBuilder,
separator: SeparatorCache,
config: &'a ProcessingConfig,
output: String,
first: bool, }
impl<'a> DirectWalker<'a> {
fn new(
input: &'a [u8],
tape: &'a [TapeEntry],
config: &'a ProcessingConfig,
separator: SeparatorCache,
output_capacity: usize,
) -> Self {
let mut output = String::with_capacity(output_capacity);
output.push('{');
Self {
input,
tape,
path: FastStreamingPathBuilder::new(),
separator,
config,
output,
first: true,
}
}
#[inline(always)]
fn finish(mut self) -> String {
self.output.push('}');
self.output
}
#[inline(always)]
fn walk_value(&mut self, idx: usize) -> usize {
if idx >= self.tape.len() {
return idx;
}
let entry = tape_entry(self.tape, idx);
match entry.kind() {
EntryKind::ObjectStart => self.walk_object(idx),
EntryKind::ArrayStart => self.walk_array(idx),
EntryKind::StringStart => {
self.emit_string_value(idx);
idx + 1
}
EntryKind::ScalarStart => {
self.emit_scalar_value(idx);
idx + 1
}
_ => idx + 1,
}
}
fn walk_object(&mut self, start_idx: usize) -> usize {
let end_idx = self.tape[start_idx].aux() as usize;
if tape_is_empty_object(self.tape, start_idx) {
if !self.config.filtering.remove_empty_objects {
self.write_value_raw(b"{}");
}
return end_idx + 1;
}
let mut cursor = start_idx + 1;
while cursor < end_idx {
let entry = tape_entry(self.tape, cursor);
if entry.kind() == EntryKind::StringStart {
let key_str = tape_content_str(self.input, entry);
self.path.push_level();
self.path.append_key_raw(key_str, &self.separator);
cursor += 1; if cursor < end_idx {
let next = tape_entry(self.tape, cursor);
if next.kind() == EntryKind::Colon {
cursor += 1;
}
}
cursor = self.walk_value(cursor);
self.path.pop_level();
} else {
cursor += 1;
}
}
end_idx + 1
}
fn walk_array(&mut self, start_idx: usize) -> usize {
let end_idx = self.tape[start_idx].aux() as usize;
if tape_is_empty_array(self.tape, start_idx) {
if !self.config.filtering.remove_empty_arrays {
self.write_value_raw(b"[]");
}
return end_idx + 1;
}
let mut cursor = start_idx + 1;
let mut index: usize = 0;
while cursor < end_idx {
let entry = tape_entry(self.tape, cursor);
if entry.kind() == EntryKind::Comma {
cursor += 1;
continue;
}
self.path.push_level();
self.path.append_index(index, &self.separator);
cursor = self.walk_value(cursor);
self.path.pop_level();
index += 1;
}
end_idx + 1
}
#[inline(always)]
fn emit_string_value(&mut self, idx: usize) {
let entry = tape_entry(self.tape, idx);
let content_len = entry.string_content_len();
if self.config.filtering.remove_empty_strings && content_len == 0 {
return;
}
let content_str = tape_content_str(self.input, entry);
let has_value_replacements = self.config.replacements.has_value_replacements();
let auto_convert_types = self.config.auto_convert_types;
if has_value_replacements || auto_convert_types {
let unescaped = if entry.string_has_escapes() {
unescape_json_string(content_str)
} else {
Cow::Borrowed(content_str)
};
if has_value_replacements {
if let Some(replaced) = apply_value_replacement_cow(
unescaped.as_ref(),
&self.config.replacements.value_replacements,
) {
if self.config.filtering.remove_empty_strings && replaced.is_empty() {
return;
}
let escaped = escape_json_string(&replaced);
self.write_leaf_owned_string(escaped.as_ref());
return;
}
}
if auto_convert_types {
if let Some(converted) = try_convert_string_to_json_bytes(unescaped.as_ref()) {
if self.config.filtering.remove_nulls && converted == "null" {
return;
}
self.write_leaf_json_fragment(&converted);
return;
}
}
}
self.write_value_raw(tape_quoted_str(self.input, entry).as_bytes());
}
#[inline(always)]
fn emit_scalar_value(&mut self, idx: usize) {
let entry = tape_entry(self.tape, idx);
let trimmed = trim_ascii(tape_scalar_bytes(self.input, entry));
if self.config.filtering.remove_nulls && trimmed == b"null" {
return;
}
self.write_value_raw(trimmed);
}
#[inline(always)]
fn write_value_raw(&mut self, value_bytes: &[u8]) {
let key = self.path.as_str();
let needed = 1 + 1 + key.len() + 2 + value_bytes.len();
self.output.reserve(needed);
if !self.first {
self.output.push(',');
}
self.first = false;
self.output.push('"');
self.output.push_str(key);
self.output.push_str("\":");
self.output
.push_str(unsafe { std::str::from_utf8_unchecked(value_bytes) });
}
#[inline(always)]
fn write_leaf_owned_string(&mut self, escaped_content: &str) {
let key = self.path.as_str();
let needed = 1 + 1 + key.len() + 3 + escaped_content.len() + 1;
self.output.reserve(needed);
if !self.first {
self.output.push(',');
}
self.first = false;
self.output.push('"');
self.output.push_str(key);
self.output.push_str("\":\"");
self.output.push_str(escaped_content);
self.output.push('"');
}
#[inline(always)]
fn write_leaf_json_fragment(&mut self, fragment: &str) {
let key = self.path.as_str();
let needed = 1 + 1 + key.len() + 2 + fragment.len();
self.output.reserve(needed);
if !self.first {
self.output.push(',');
}
self.first = false;
self.output.push('"');
self.output.push_str(key);
self.output.push_str("\":");
self.output.push_str(fragment);
}
}
struct CollectingWalker<'a> {
input: &'a [u8],
tape: &'a [TapeEntry],
path: FastStreamingPathBuilder,
separator: SeparatorCache,
config: &'a ProcessingConfig,
entries: Vec<CollectedEntry<'a>>,
}
impl<'a> CollectingWalker<'a> {
fn new(
input: &'a [u8],
tape: &'a [TapeEntry],
config: &'a ProcessingConfig,
separator: SeparatorCache,
capacity: usize,
) -> Self {
Self {
input,
tape,
path: FastStreamingPathBuilder::new(),
separator,
config,
entries: Vec::with_capacity(capacity),
}
}
#[inline(always)]
fn walk_value(&mut self, idx: usize) -> usize {
if idx >= self.tape.len() {
return idx;
}
let entry = tape_entry(self.tape, idx);
match entry.kind() {
EntryKind::ObjectStart => self.walk_object(idx),
EntryKind::ArrayStart => self.walk_array(idx),
EntryKind::StringStart => {
self.emit_string_value(idx);
idx + 1
}
EntryKind::ScalarStart => {
self.emit_scalar_value(idx);
idx + 1
}
_ => idx + 1,
}
}
fn walk_object(&mut self, start_idx: usize) -> usize {
let end_idx = self.tape[start_idx].aux() as usize;
if tape_is_empty_object(self.tape, start_idx) {
if !self.config.filtering.remove_empty_objects {
self.collect_value(ValueRef::Raw(b"{}"));
}
return end_idx + 1;
}
let mut cursor = start_idx + 1;
while cursor < end_idx {
let entry = tape_entry(self.tape, cursor);
if entry.kind() == EntryKind::StringStart {
let key_str = tape_content_str(self.input, entry);
self.path.push_level();
if entry.string_has_escapes() {
let unescaped = unescape_json_string(key_str);
self.path
.append_key_raw(unescaped.as_ref(), &self.separator);
} else {
self.path.append_key_raw(key_str, &self.separator);
}
cursor += 1;
if cursor < end_idx {
let next = tape_entry(self.tape, cursor);
if next.kind() == EntryKind::Colon {
cursor += 1;
}
}
cursor = self.walk_value(cursor);
self.path.pop_level();
} else {
cursor += 1;
}
}
end_idx + 1
}
fn walk_array(&mut self, start_idx: usize) -> usize {
let end_idx = self.tape[start_idx].aux() as usize;
if tape_is_empty_array(self.tape, start_idx) {
if !self.config.filtering.remove_empty_arrays {
self.collect_value(ValueRef::Raw(b"[]"));
}
return end_idx + 1;
}
let mut cursor = start_idx + 1;
let mut index: usize = 0;
while cursor < end_idx {
let entry = tape_entry(self.tape, cursor);
if entry.kind() == EntryKind::Comma {
cursor += 1;
continue;
}
self.path.push_level();
self.path.append_index(index, &self.separator);
cursor = self.walk_value(cursor);
self.path.pop_level();
index += 1;
}
end_idx + 1
}
#[inline(always)]
fn emit_string_value(&mut self, idx: usize) {
let entry = tape_entry(self.tape, idx);
let content_len = entry.string_content_len();
if self.config.filtering.remove_empty_strings && content_len == 0 {
return;
}
let content_str = tape_content_str(self.input, entry);
let has_value_replacements = self.config.replacements.has_value_replacements();
let auto_convert_types = self.config.auto_convert_types;
if has_value_replacements || auto_convert_types {
let unescaped = if entry.string_has_escapes() {
unescape_json_string(content_str)
} else {
Cow::Borrowed(content_str)
};
if has_value_replacements {
if let Some(replaced) = apply_value_replacement_cow(
unescaped.as_ref(),
&self.config.replacements.value_replacements,
) {
if self.config.filtering.remove_empty_strings && replaced.is_empty() {
return;
}
let escaped = escape_json_string(&replaced);
let mut buf = String::with_capacity(escaped.len() + 2);
buf.push('"');
buf.push_str(&escaped);
buf.push('"');
self.collect_value(ValueRef::Owned(buf));
return;
}
}
if auto_convert_types {
if let Some(converted) = try_convert_string_to_json_bytes(unescaped.as_ref()) {
if self.config.filtering.remove_nulls && *converted == *"null" {
return;
}
self.collect_value(ValueRef::Owned(converted.into_owned()));
return;
}
}
}
self.collect_value(ValueRef::Raw(tape_quoted_str(self.input, entry).as_bytes()));
}
#[inline(always)]
fn emit_scalar_value(&mut self, idx: usize) {
let entry = tape_entry(self.tape, idx);
let trimmed = trim_ascii(tape_scalar_bytes(self.input, entry));
if self.config.filtering.remove_nulls && trimmed == b"null" {
return;
}
self.collect_value(ValueRef::Raw(trimmed));
}
#[inline]
fn collect_value(&mut self, value: ValueRef<'a>) {
let mut key = CompactString::from(self.path.as_str());
if self.config.lowercase_keys {
key.make_ascii_lowercase();
}
if self.config.replacements.has_key_replacements() {
if let Ok(Some(new_key)) =
apply_key_replacement_patterns(&key, &self.config.replacements.key_replacements)
{
key = CompactString::from(new_key);
}
}
self.entries.push(CollectedEntry { key, value });
}
}
fn resolve_and_write(entries: Vec<CollectedEntry<'_>>, config: &ProcessingConfig) -> String {
if entries.is_empty() {
return "{}".to_string();
}
let has_collision_handling = config.collision.has_collision_handling();
if !has_collision_handling
&& !config.lowercase_keys
&& !config.replacements.has_key_replacements()
{
return write_entries_simple(&entries);
}
let n = entries.len();
let mut key_indices: FxHashMap<&str, SmallVec<[usize; 1]>> =
FxHashMap::with_capacity_and_hasher(n, Default::default());
let mut ordered_keys: Vec<usize> = Vec::with_capacity(n);
for (i, entry) in entries.iter().enumerate() {
let key: &str = &entry.key;
key_indices
.entry(key)
.and_modify(|indices| indices.push(i))
.or_insert_with(|| {
ordered_keys.push(i);
SmallVec::from_elem(i, 1)
});
}
let output_cap = entries
.iter()
.map(|e| {
e.key.len()
+ 4
+ match &e.value {
ValueRef::Raw(b) => b.len(),
ValueRef::Owned(s) => s.len(),
}
})
.sum::<usize>()
+ ordered_keys.len().saturating_sub(1) + 2;
let mut output = String::with_capacity(output_cap);
output.push('{');
let mut first = true;
for &first_idx in &ordered_keys {
let key = &entries[first_idx].key;
let indices = &key_indices[key.as_str()];
if !first {
output.push(',');
}
first = false;
output.push('"');
write_json_escaped_key(&mut output, key);
output.push_str("\":");
if indices.len() == 1 {
write_value_ref(&mut output, &entries[indices[0]].value);
} else if has_collision_handling {
output.push('[');
for (j, &idx) in indices.iter().enumerate() {
if j > 0 {
output.push(',');
}
write_value_ref(&mut output, &entries[idx].value);
}
output.push(']');
} else {
write_value_ref(
&mut output,
&entries[*indices
.last()
.expect("collision indices non-empty: at least one index per key")]
.value,
);
}
}
output.push('}');
output
}
#[inline]
fn write_entries_simple(entries: &[CollectedEntry<'_>]) -> String {
let output_cap = entries
.iter()
.map(|e| {
e.key.len()
+ 4
+ match &e.value {
ValueRef::Raw(b) => b.len(),
ValueRef::Owned(s) => s.len(),
}
})
.sum::<usize>()
+ entries.len().saturating_sub(1) + 2;
let mut output = String::with_capacity(output_cap);
output.push('{');
for (i, entry) in entries.iter().enumerate() {
if i > 0 {
output.push(',');
}
output.push('"');
write_json_escaped_key(&mut output, &entry.key);
output.push_str("\":");
write_value_ref(&mut output, &entry.value);
}
output.push('}');
output
}
#[inline(always)]
fn write_value_ref(output: &mut String, value: &ValueRef<'_>) {
match value {
ValueRef::Raw(bytes) => {
output.push_str(unsafe { std::str::from_utf8_unchecked(bytes) });
}
ValueRef::Owned(s) => {
output.push_str(s);
}
}
}
static NEEDS_JSON_ESCAPE: [bool; 256] = {
let mut table = [false; 256];
let mut i = 0u16;
while i < 0x20 {
table[i as usize] = true;
i += 1;
}
table[b'"' as usize] = true;
table[b'\\' as usize] = true;
table
};
#[inline(always)]
fn needs_json_escape(bytes: &[u8]) -> bool {
bytes.iter().any(|&b| NEEDS_JSON_ESCAPE[b as usize])
}
#[inline(always)]
pub(crate) fn write_json_escaped_key(output: &mut String, key: &str) {
let bytes = key.as_bytes();
if !needs_json_escape(bytes) {
output.push_str(key);
return;
}
let mut run_start = 0;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if NEEDS_JSON_ESCAPE[b as usize] {
if run_start < i {
output.push_str(&key[run_start..i]);
}
match b {
b'"' => output.push_str("\\\""),
b'\\' => output.push_str("\\\\"),
b'\n' => output.push_str("\\n"),
b'\r' => output.push_str("\\r"),
b'\t' => output.push_str("\\t"),
_ => {
let hex = b"0123456789abcdef";
output.push_str("\\u00");
output.push(hex[(b >> 4) as usize] as char);
output.push(hex[(b & 0x0F) as usize] as char);
}
}
i += 1;
run_start = i;
} else {
i += 1;
}
}
if run_start < bytes.len() {
output.push_str(&key[run_start..]);
}
}
#[inline]
pub(crate) fn unescape_json_string(s: &str) -> Cow<'_, str> {
if memchr(b'\\', s.as_bytes()).is_none() {
return Cow::Borrowed(s);
}
let mut result = String::with_capacity(s.len());
let bytes = s.as_bytes();
let mut i = 0;
let mut run_start = 0;
while i < bytes.len() {
if bytes[i] == b'\\' && i + 1 < bytes.len() {
if run_start < i {
result.push_str(&s[run_start..i]);
}
match bytes[i + 1] {
b'"' => {
result.push('"');
i += 2;
}
b'\\' => {
result.push('\\');
i += 2;
}
b'/' => {
result.push('/');
i += 2;
}
b'n' => {
result.push('\n');
i += 2;
}
b'r' => {
result.push('\r');
i += 2;
}
b't' => {
result.push('\t');
i += 2;
}
b'b' => {
result.push('\u{0008}');
i += 2;
}
b'f' => {
result.push('\u{000C}');
i += 2;
}
b'u' if i + 5 < bytes.len() => {
let hex = &s[i + 2..i + 6];
if let Ok(code) = u16::from_str_radix(hex, 16) {
if let Some(ch) = char::from_u32(code as u32) {
result.push(ch);
} else {
result.push_str(&s[i..i + 6]);
}
} else {
result.push_str(&s[i..i + 6]);
}
i += 6;
}
_ => {
result.push('\\');
i += 1;
}
}
run_start = i;
} else if bytes[i] == b'\\' {
i += 1;
} else {
i += 1;
}
}
if run_start < bytes.len() {
result.push_str(&s[run_start..]);
}
Cow::Owned(result)
}
#[inline]
pub(crate) fn escape_json_string(s: &str) -> Cow<'_, str> {
let bytes = s.as_bytes();
if !needs_json_escape(bytes) {
return Cow::Borrowed(s);
}
let mut result = String::with_capacity(s.len() + 8);
let mut run_start = 0;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if NEEDS_JSON_ESCAPE[b as usize] {
if run_start < i {
result.push_str(&s[run_start..i]);
}
match b {
b'"' => result.push_str("\\\""),
b'\\' => result.push_str("\\\\"),
b'\n' => result.push_str("\\n"),
b'\r' => result.push_str("\\r"),
b'\t' => result.push_str("\\t"),
_ => {
let hex = b"0123456789abcdef";
result.push_str("\\u00");
result.push(hex[(b >> 4) as usize] as char);
result.push(hex[(b & 0x0F) as usize] as char);
}
}
i += 1;
run_start = i;
} else {
i += 1;
}
}
if run_start < bytes.len() {
result.push_str(&s[run_start..]);
}
Cow::Owned(result)
}
#[inline(always)]
pub(crate) fn trim_ascii(bytes: &[u8]) -> &[u8] {
let mut start = 0;
while start < bytes.len()
&& matches!(
unsafe { *bytes.get_unchecked(start) },
b' ' | b'\t' | b'\n' | b'\r'
)
{
start += 1;
}
let mut end = bytes.len();
while end > start
&& matches!(
unsafe { *bytes.get_unchecked(end - 1) },
b' ' | b'\t' | b'\n' | b'\r'
)
{
end -= 1;
}
unsafe { bytes.get_unchecked(start..end) }
}
#[inline(always)]
pub(crate) fn apply_value_replacement_cow(
value: &str,
replacements: &[(String, String)],
) -> Option<String> {
let mut current = Cow::Borrowed(value);
let mut changed = false;
for (pattern, replacement) in replacements {
match parse_pattern(pattern) {
ParsedPattern::Regex(inner) => {
if let Ok(regex) = get_cached_regex(inner) {
if let Cow::Owned(s) = regex.replace_all(¤t, replacement.as_str()) {
current = Cow::Owned(s);
changed = true;
}
}
}
ParsedPattern::Literal(lit) => {
if memchr::memmem::find(current.as_bytes(), lit.as_bytes()).is_some() {
current = Cow::Owned(current.replace(lit, replacement));
changed = true;
}
}
}
}
if changed {
Some(current.into_owned())
} else {
None
}
}
#[inline]
fn count_top_level_children(tape: &[TapeEntry]) -> usize {
if tape.is_empty() {
return 0;
}
let root = tape[0];
let end_idx = root.aux() as usize;
let mut count = 0usize;
let mut cursor = 1;
match root.kind() {
EntryKind::ObjectStart => {
while cursor < end_idx && cursor < tape.len() {
if tape[cursor].kind() == EntryKind::StringStart {
count += 1;
cursor += 1; if cursor < tape.len() && tape[cursor].kind() == EntryKind::Colon {
cursor += 1;
}
cursor = skip_tape_value(tape, cursor);
} else {
cursor += 1;
}
}
}
EntryKind::ArrayStart => {
while cursor < end_idx && cursor < tape.len() {
if tape[cursor].kind() == EntryKind::Comma {
cursor += 1;
continue;
}
count += 1;
cursor = skip_tape_value(tape, cursor);
}
}
_ => {}
}
count
}
#[inline(always)]
pub(crate) fn skip_tape_value(tape: &[TapeEntry], idx: usize) -> usize {
if idx >= tape.len() {
return idx;
}
match tape[idx].kind() {
EntryKind::ObjectStart | EntryKind::ArrayStart => {
let end = tape[idx].aux() as usize;
end + 1
}
_ => idx + 1,
}
}
fn collect_child_ranges(tape: &[TapeEntry]) -> Vec<(usize, usize, usize)> {
if tape.is_empty() {
return Vec::new();
}
let root = tape[0];
let end_idx = root.aux() as usize;
let mut ranges = Vec::new();
let mut cursor = 1;
match root.kind() {
EntryKind::ObjectStart => {
while cursor < end_idx && cursor < tape.len() {
if tape[cursor].kind() == EntryKind::StringStart {
let key_idx = cursor;
cursor += 1;
if cursor < tape.len() && tape[cursor].kind() == EntryKind::Colon {
cursor += 1;
}
let val_idx = cursor;
ranges.push((key_idx, val_idx, 0));
cursor = skip_tape_value(tape, cursor);
} else {
cursor += 1;
}
}
}
EntryKind::ArrayStart => {
let mut index = 0usize;
while cursor < end_idx && cursor < tape.len() {
if tape[cursor].kind() == EntryKind::Comma {
cursor += 1;
continue;
}
ranges.push((usize::MAX, cursor, index));
cursor = skip_tape_value(tape, cursor);
index += 1;
}
}
_ => {}
}
ranges
}
fn flatten_collecting_parallel<'a>(
input: &'a [u8],
tape: &'a [TapeEntry],
config: &'a ProcessingConfig,
separator: &SeparatorCache,
ranges: &[(usize, usize, usize)],
is_root_object: bool,
) -> Result<Vec<CollectedEntry<'a>>, JsonToolsError> {
let n_threads = config.effective_thread_count(ranges.len());
let chunk_size = ranges.len().div_ceil(n_threads);
let process_chunks = || -> Vec<CollectedEntry<'a>> {
ranges
.par_chunks(chunk_size)
.flat_map(|range_chunk| {
let sep = SeparatorCache::new(&separator.separator);
let mut walker =
CollectingWalker::new(input, tape, config, sep, range_chunk.len() * 4);
for &(key_idx, val_idx, arr_idx) in range_chunk {
walker.path.buffer.clear();
walker.path.stack.clear();
if is_root_object {
let key_entry = tape[key_idx];
let key_offset = key_entry.offset() + 1;
let key_len = key_entry.string_content_len();
let key_bytes = &input[key_offset..key_offset + key_len];
let key_str = unsafe { std::str::from_utf8_unchecked(key_bytes) };
walker.path.push_level();
if key_entry.string_has_escapes() {
let key = unescape_json_string(key_str);
walker.path.append_key_raw(key.as_ref(), &walker.separator);
} else {
walker.path.append_key_raw(key_str, &walker.separator);
}
} else {
walker.path.push_level();
walker.path.append_index(arr_idx, &walker.separator);
}
walker.walk_value(val_idx);
walker.path.pop_level();
}
walker.entries
})
.collect()
};
let merged = match config.num_threads {
None => process_chunks(),
Some(n) => {
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(n)
.build()
.map_err(|e| {
JsonToolsError::configuration_error(format!(
"failed to build thread pool with {n} threads: {e}"
))
})?;
pool.install(process_chunks)
}
};
Ok(merged)
}
#[inline]
fn needs_collecting_path(config: &ProcessingConfig) -> bool {
config.lowercase_keys
|| config.replacements.has_key_replacements()
|| config.collision.has_collision_handling()
}
#[inline]
pub(crate) fn process_single_json(
json: &str,
config: &ProcessingConfig,
) -> Result<String, JsonToolsError> {
let input = json.as_bytes();
if input.len() > u32::MAX as usize {
return Err(JsonToolsError::input_validation_error(
"Input exceeds 4 GiB limit",
));
}
let start = {
let mut p = 0;
let len = input.len();
while p < len {
let b = unsafe { *input.get_unchecked(p) };
if b > 0x20 || (b != b' ' && b != b'\t' && b != b'\n' && b != b'\r') {
break;
}
p += 1;
}
p
};
if start >= input.len() {
return Err(JsonToolsError::input_validation_error("Empty JSON input"));
}
let first = unsafe { *input.get_unchecked(start) };
if first != b'{' && first != b'[' {
let mut value = json_parser::parse_json(json)?;
if !config.replacements.value_replacements.is_empty() {
apply_value_replacement_patterns(&mut value, &config.replacements.value_replacements)?;
}
return json_parser::to_string(&value).map_err(JsonToolsError::serialization_error);
}
let after_open = {
let mut p = start + 1;
let len = input.len();
while p < len {
let b = unsafe { *input.get_unchecked(p) };
if b > 0x20 || (b != b' ' && b != b'\t' && b != b'\n' && b != b'\r') {
break;
}
p += 1;
}
p
};
if after_open < input.len() {
let close = unsafe { *input.get_unchecked(after_open) };
if (first == b'{' && close == b'}') || (first == b'[' && close == b']') {
return Ok("{}".to_string());
}
}
let tape = scan_and_fixup(input)?;
let separator = SeparatorCache::new(&config.separator);
if needs_collecting_path(config) {
let leaf_estimate = tape.len() / 4;
let child_count = count_top_level_children(&tape);
let entries = if child_count > config.nested_parallel_threshold {
let ranges = collect_child_ranges(&tape);
let is_root_object = tape[0].kind() == EntryKind::ObjectStart;
flatten_collecting_parallel(input, &tape, config, &separator, &ranges, is_root_object)?
} else {
let mut walker = CollectingWalker::new(input, &tape, config, separator, leaf_estimate);
if !tape.is_empty() {
walker.walk_value(0);
}
walker.entries
};
Ok(resolve_and_write(entries, config))
} else {
let mut walker = DirectWalker::new(input, &tape, config, separator, input.len() * 3 / 2);
if !tape.is_empty() {
walker.walk_value(0);
}
Ok(walker.finish())
}
}