simdjson 5.0.2
Ridiculously Fast JSON
Loading...
Searching...
No Matches
fractured_json-inl.h
1#ifndef SIMDJSON_DOM_FRACTURED_JSON_INL_H
2#define SIMDJSON_DOM_FRACTURED_JSON_INL_H
3
4#include "simdjson/dom/fractured_json.h"
5#include "simdjson/dom/serialization.h"
6#include "simdjson/dom/element-inl.h"
7#include "simdjson/dom/array-inl.h"
8#include "simdjson/dom/object-inl.h"
9#include "simdjson/dom/parser-inl.h"
10#include "simdjson/padded_string.h"
11#include "simdjson/internal/json_structure_analyzer.h"
12#include "simdjson/internal/fractured_formatter.h"
13
14#include <cmath>
15#include <algorithm>
16#include <cstring>
17#include <iterator>
18#include <unordered_map>
19
20namespace simdjson {
21namespace internal {
22
23//
24// Structure Analyzer Implementation
25//
26
27inline element_metrics structure_analyzer::analyze(const dom::element& elem,
28 const fractured_json_options& opts) {
29 current_opts_ = &opts;
30 return analyze_element(elem, 0);
31}
32
33inline void structure_analyzer::clear() {
34 current_opts_ = nullptr;
35}
36
37inline element_metrics structure_analyzer::analyze_array(const dom::array& arr,
38 const fractured_json_options& opts) {
39 current_opts_ = &opts;
40 return analyze_array(arr, 0);
41}
42
43inline element_metrics structure_analyzer::analyze_object(const dom::object& obj,
44 const fractured_json_options& opts) {
45 current_opts_ = &opts;
46 return analyze_object(obj, 0);
47}
48
49inline element_metrics structure_analyzer::analyze_element(const dom::element& elem, size_t depth) const {
50 switch (elem.type()) {
52 dom::array arr;
53 if (elem.get_array().get(arr) == SUCCESS) {
54 return analyze_array(arr, depth);
55 }
56 break;
57 }
59 dom::object obj;
60 if (elem.get_object().get(obj) == SUCCESS) {
61 return analyze_object(obj, depth);
62 }
63 break;
64 }
65 default:
66 // Handle all scalar types with a helper
67 return analyze_scalar(elem);
68 }
69 return element_metrics{};
70}
71
72inline element_metrics structure_analyzer::analyze_scalar(const dom::element& elem) const {
73 element_metrics metrics;
74 metrics.complexity = 0;
75 metrics.child_count = 0;
76 metrics.can_inline = true;
77
78 switch (elem.type()) {
80 std::string_view str;
81 if (elem.get_string().get(str) == SUCCESS) {
82 metrics.estimated_inline_len = estimate_string_length(str);
83 }
84 break;
85 }
87 int64_t val;
88 if (elem.get_int64().get(val) == SUCCESS) {
89 metrics.estimated_inline_len = estimate_number_length(val);
90 }
91 break;
92 }
94 uint64_t val;
95 if (elem.get_uint64().get(val) == SUCCESS) {
96 metrics.estimated_inline_len = estimate_number_length(val);
97 }
98 break;
99 }
101 double val;
102 if (elem.get_double().get(val) == SUCCESS) {
103 metrics.estimated_inline_len = estimate_number_length(val);
104 }
105 break;
106 }
108 bool val;
109 if (elem.get_bool().get(val) == SUCCESS) {
110 metrics.estimated_inline_len = val ? 4 : 5; // "true" or "false"
111 }
112 break;
113 }
115 metrics.estimated_inline_len = 4; // "null"
116 break;
117 default:
118 break;
119 }
120
121 return metrics;
122}
123
124inline element_metrics structure_analyzer::analyze_array(const dom::array& arr,
125 size_t depth) const {
126 element_metrics metrics;
127 metrics.complexity = 1; // At least 1 for being an array
128 metrics.estimated_inline_len = 2; // "[]"
129 metrics.child_count = 0;
130
131 size_t max_child_complexity = 0;
132 bool first = true;
133
134 for (dom::element child : arr) {
135 if (!first) {
136 metrics.estimated_inline_len += 2; // ", "
137 }
138 first = false;
139
140 element_metrics child_metrics = analyze_element(child, depth + 1);
141 metrics.estimated_inline_len += child_metrics.estimated_inline_len;
142 max_child_complexity = (std::max)(max_child_complexity, child_metrics.complexity);
143 metrics.child_count++;
144 metrics.children.push_back(std::move(child_metrics));
145 }
146
147 // Complexity is 1 + max child complexity
148 metrics.complexity = 1 + max_child_complexity;
149
150 // Bracket padding "[ 1, 2 ]" vs "[1, 2]"
151 bool use_bracket_padding = (max_child_complexity >= 1)
152 ? current_opts_->nested_bracket_padding : current_opts_->simple_bracket_padding;
153 if (use_bracket_padding && metrics.child_count > 0) {
154 metrics.estimated_inline_len += 2;
155 }
156
157 // Check if can inline
158 metrics.can_inline = metrics.complexity <= current_opts_->max_inline_complexity;
159
160 // Check for uniform array (table formatting, or aligned compact multiline).
161 bool wants_table_columns =
162 (current_opts_->enable_table_format &&
163 max_child_complexity <= current_opts_->max_table_row_complexity) ||
164 (current_opts_->enable_compact_multiline &&
165 max_child_complexity <= current_opts_->max_compact_array_complexity);
166 if (wants_table_columns) {
167 metrics.is_uniform_array = check_array_uniformity(arr, metrics, depth);
168 }
169
170 return metrics;
171}
172
173inline element_metrics structure_analyzer::analyze_object(const dom::object& obj,
174 size_t depth) const {
175 element_metrics metrics;
176 metrics.complexity = 1;
177 metrics.estimated_inline_len = 2; // "{}"
178 metrics.child_count = 0;
179
180 size_t max_child_complexity = 0;
181 bool first = true;
182
183 for (dom::key_value_pair field : obj) {
184 if (!first) {
185 metrics.estimated_inline_len += 2; // ", "
186 }
187 first = false;
188
189 // Key length: quotes + key + colon + space
190 metrics.estimated_inline_len += estimate_string_length(field.key) + 2;
191
192 element_metrics child_metrics = analyze_element(field.value, depth + 1);
193 metrics.estimated_inline_len += child_metrics.estimated_inline_len;
194 max_child_complexity = (std::max)(max_child_complexity, child_metrics.complexity);
195 metrics.child_count++;
196 metrics.children.push_back(std::move(child_metrics));
197 }
198
199 metrics.complexity = 1 + max_child_complexity;
200
201 // Bracket padding '{ "a": 1 }' vs '{"a": 1}'
202 bool use_bracket_padding = (max_child_complexity >= 1)
203 ? current_opts_->nested_bracket_padding : current_opts_->simple_bracket_padding;
204 if (use_bracket_padding && metrics.child_count > 0) {
205 metrics.estimated_inline_len += 2;
206 }
207
208 metrics.can_inline = metrics.complexity <= current_opts_->max_inline_complexity;
209
210 return metrics;
211}
212
213inline size_t structure_analyzer::estimate_string_length(std::string_view s) const {
214 size_t len = 2; // quotes
215 for (char c : s) {
216 if (c == '"' || c == '\\' || static_cast<unsigned char>(c) < 32) {
217 len += 2; // escape sequence (at least)
218 } else {
219 len += 1;
220 }
221 }
222 return len;
223}
224
225inline size_t structure_analyzer::estimate_number_length(double d) const {
226 if (!std::isfinite(d)) {
227#if SIMDJSON_ENABLE_NAN_INF
228 if (std::isnan(d)) {
229 return 3; // "NaN"
230 } else if (d < 0) {
231 return 9; // "-Infinity"
232 } else {
233 return 8; // "Infinity"
234 }
235#else
236 return 4; // "null" for invalid numbers
237#endif
238 }
239 // Rough estimate: up to 17 significant digits + sign + decimal point + exponent
240 char buf[32];
241 int len = snprintf(buf, sizeof(buf), "%.17g", d);
242 return len > 0 ? static_cast<size_t>(len) : 20;
243}
244
245inline size_t structure_analyzer::estimate_number_length(int64_t i) const {
246 if (i == 0) return 1;
247 // Handle INT64_MIN specially to avoid overflow when negating
248 if (i == INT64_MIN) return 20; // "-9223372036854775808" is 20 characters
249 size_t len = (i < 0) ? 1 : 0; // negative sign
250 int64_t abs_val = (i < 0) ? -i : i;
251 while (abs_val > 0) {
252 len++;
253 abs_val /= 10;
254 }
255 return len;
256}
257
258inline size_t structure_analyzer::estimate_number_length(uint64_t u) const {
259 if (u == 0) return 1;
260 size_t len = 0;
261 while (u > 0) {
262 len++;
263 u /= 10;
264 }
265 return len;
266}
267
268inline table_column_type structure_analyzer::classify_table_value(dom::element_type type) {
269 switch (type) {
270 case dom::element_type::OBJECT: return table_column_type::object;
271 case dom::element_type::ARRAY: return table_column_type::array;
274 case dom::element_type::DOUBLE: return table_column_type::number;
275 case dom::element_type::NULL_VALUE: return table_column_type::unknown;
276 default: return table_column_type::simple; // string, bool
277 }
278}
279
280inline void structure_analyzer::classify_and_measure(
281 const std::vector<std::pair<dom::element, const element_metrics*>>& values,
282 table_column_type& common, size_t& max_width) {
283 common = table_column_type::unknown;
284 max_width = 0;
285 for (const auto& v : values) {
286 table_column_type t = classify_table_value(v.first.type());
287 if (t != table_column_type::unknown) {
288 if (common == table_column_type::unknown) common = t;
289 else if (t != common) common = table_column_type::mixed;
290 }
291 if (v.second) {
292 max_width = (std::max)(max_width, v.second->estimated_inline_len);
293 }
294 }
295}
296
297inline void structure_analyzer::build_table_columns(
298 const std::vector<std::pair<dom::element, const element_metrics*>>& values,
299 std::vector<table_column>& out_columns) const {
300 out_columns.clear();
301 if (values.empty()) {
302 return;
303 }
304
305 table_column_type common = table_column_type::unknown;
306 for (const auto& v : values) {
307 table_column_type t = classify_table_value(v.first.type());
308 if (t == table_column_type::unknown) continue;
309 if (common == table_column_type::unknown) common = t;
310 else if (t != common) { common = table_column_type::mixed; break; }
311 }
312 if (common != table_column_type::object && common != table_column_type::array) {
313 return;
314 }
315
316 std::vector<std::vector<std::pair<dom::element, const element_metrics*>>> per_column_values;
317
318 if (common == table_column_type::object) {
319 std::unordered_map<std::string_view, size_t> column_index;
320 for (const auto& v : values) {
321 if (v.first.type() != dom::element_type::OBJECT) continue;
322 dom::object obj;
323 if (v.first.get_object().get(obj) != SUCCESS) continue;
324
325 size_t field_idx = 0;
326 for (dom::key_value_pair field : obj) {
327 auto it = column_index.find(field.key);
328 size_t col_idx;
329 if (it == column_index.end()) {
330 col_idx = out_columns.size();
331 column_index.emplace(field.key, col_idx);
332 out_columns.emplace_back();
333 out_columns.back().key.assign(field.key.data(), field.key.size());
334 out_columns.back().key_width = estimate_string_length(field.key);
335 per_column_values.emplace_back();
336 } else {
337 col_idx = it->second;
338 }
339 const element_metrics* field_metrics = (v.second && field_idx < v.second->children.size())
340 ? &v.second->children[field_idx] : nullptr;
341 per_column_values[col_idx].emplace_back(field.value, field_metrics);
342 field_idx++;
343 }
344 }
345 } else { // array: columns by position
346 for (const auto& v : values) {
347 if (v.first.type() != dom::element_type::ARRAY) continue;
348 dom::array sub_arr;
349 if (v.first.get_array().get(sub_arr) != SUCCESS) continue;
350
351 size_t idx = 0;
352 for (dom::element item : sub_arr) {
353 if (out_columns.size() <= idx) {
354 out_columns.emplace_back();
355 per_column_values.emplace_back();
356 }
357 const element_metrics* item_metrics = (v.second && idx < v.second->children.size())
358 ? &v.second->children[idx] : nullptr;
359 per_column_values[idx].emplace_back(item, item_metrics);
360 idx++;
361 }
362 }
363 }
364
365 for (size_t i = 0; i < out_columns.size(); i++) {
366 table_column_type col_type;
367 size_t max_width;
368 classify_and_measure(per_column_values[i], col_type, max_width);
369 out_columns[i].type = col_type;
370 out_columns[i].plain_width = max_width;
371
372 if (col_type == table_column_type::object || col_type == table_column_type::array) {
373 build_table_columns(per_column_values[i], out_columns[i].children);
374 }
375
376 out_columns[i].width = out_columns[i].children.empty() ? max_width : compute_columns_width(out_columns[i].children);
377 }
378}
379
380inline bool structure_analyzer::check_array_uniformity(const dom::array& arr,
381 element_metrics& metrics,
382 size_t depth) const {
383 std::vector<std::pair<dom::element, const element_metrics*>> values;
384 values.reserve(metrics.child_count);
385
386 size_t row_idx = 0;
387 for (dom::element elem : arr) {
388 const element_metrics* row_metrics = (row_idx < metrics.children.size()) ? &metrics.children[row_idx] : nullptr;
389 values.emplace_back(elem, row_metrics);
390 row_idx++;
391 }
392
393 build_table_columns(values, metrics.table_columns);
394 if (metrics.table_columns.empty()) {
395 // Not uniformly object or array. Check for uniform scalar
396 table_column_type common;
397 size_t max_width;
398 classify_and_measure(values, common, max_width);
399 if (common != table_column_type::number && common != table_column_type::simple) {
400 return false;
401 }
402 metrics.scalar_column_type = common;
403 metrics.table_row_width = max_width;
404 metrics.table_row_width_full = max_width;
405 return true;
406 }
407
408 metrics.table_row_width_full = compute_columns_width(metrics.table_columns);
409
410 size_t row_indent_width = (depth + 1) * current_opts_->indent_spaces;
411 size_t budget = (row_indent_width + 1 >= current_opts_->max_total_line_length)
412 ? 0 : current_opts_->max_total_line_length - row_indent_width - 1;
413
414 size_t width = metrics.table_row_width_full;
415 while (width > budget && flatten_deepest_columns(metrics.table_columns)) {
416 recompute_column_widths(metrics.table_columns);
417 width = compute_columns_width(metrics.table_columns);
418 }
419
420 metrics.table_row_width = width;
421 return true;
422}
423
424inline size_t structure_analyzer::compute_columns_width(const std::vector<table_column>& columns) const {
425 size_t width = 2; // "{}" or "[]"
426 if (table_row_is_nested(columns) ? current_opts_->nested_bracket_padding : current_opts_->simple_bracket_padding) {
427 width += 2;
428 }
429
430 for (const table_column& col : columns) {
431 if (!col.key.empty()) {
432 width += col.key_width;
433 width += current_opts_->colon_padding ? 2 : 1;
434 }
435 width += col.width;
436 }
437 if (columns.size() > 1) {
438 width += (columns.size() - 1) * (current_opts_->comma_padding ? 2 : 1);
439 }
440 return width;
441}
442
443inline size_t structure_analyzer::column_height(const table_column& column) {
444 size_t height = 0;
445 for (const table_column& child : column.children) {
446 height = (std::max)(height, column_height(child));
447 }
448 return column.children.empty() ? 0 : height + 1;
449}
450
451inline bool structure_analyzer::flatten_deepest_columns(std::vector<table_column>& columns) {
452 size_t max_height = 0;
453 for (const table_column& col : columns) {
454 max_height = (std::max)(max_height, column_height(col));
455 }
456
457 bool changed = false;
458 for (table_column& col : columns) {
459 if (column_height(col) != max_height || max_height == 0) continue;
460 if (max_height == 1) {
461 col.children.clear();
462 col.width = col.plain_width;
463 changed = true;
464 } else {
465 changed |= flatten_deepest_columns(col.children);
466 }
467 }
468 return changed;
469}
470
471inline void structure_analyzer::recompute_column_widths(std::vector<table_column>& columns) const {
472 for (table_column& col : columns) {
473 if (!col.children.empty()) {
474 recompute_column_widths(col.children);
475 col.width = compute_columns_width(col.children);
476 }
477 }
478}
479
480inline layout_mode structure_analyzer::decide_layout(const element_metrics& metrics,
481 size_t depth,
482 const fractured_json_options& opts,
483 bool has_trailing_comma) {
484 if (metrics.child_count == 0) {
485 return layout_mode::single_line;
486 }
487
488 long long signed_depth = static_cast<long long>(depth);
489 bool depth_allows_inline_or_compact = signed_depth > opts.always_expand_depth;
490 bool depth_allows_table = signed_depth >= opts.always_expand_depth;
491
492 // Check inline feasibility
493 size_t reserved_width = depth * opts.indent_spaces + (has_trailing_comma ? 1 : 0);
494 if (depth_allows_inline_or_compact && metrics.can_inline &&
495 metrics.estimated_inline_len + reserved_width <= opts.max_total_line_length) {
496 return layout_mode::single_line;
497 }
498
499 // Rows (table's or compact multiline's) render one level deeper than the
500 // array itself.
501 size_t row_indent_width = (depth + 1) * opts.indent_spaces;
502
503 // Check compact multiline
504 // for uniform arrays fall back to table if we would have to flatten any formatting
505 bool compact_multiline_enabled = opts.enable_compact_multiline &&
506 metrics.complexity <= opts.max_compact_array_complexity + 1 &&
507 metrics.child_count >= opts.min_compact_array_row_items;
508 if (depth_allows_inline_or_compact && compact_multiline_enabled) {
509 bool aligned = metrics.is_uniform_array;
510 size_t comma_width = opts.comma_padding ? 2 : 1;
511 size_t avg_item_width;
512 if (aligned) {
513 avg_item_width = metrics.table_row_width_full + comma_width;
514 } else {
515 size_t sum = 0;
516 for (const element_metrics& child : metrics.children) {
517 sum += child.estimated_inline_len;
518 }
519 avg_item_width = comma_width + sum / metrics.child_count;
520 }
521
522 size_t row_pack_space = (row_indent_width >= opts.max_total_line_length)
523 ? 0 : opts.max_total_line_length - row_indent_width;
524 if (avg_item_width * opts.min_compact_array_row_items <= row_pack_space) {
525 return layout_mode::compact_multiline;
526 }
527 }
528
529 // Check Table mode
530 if (depth_allows_table && opts.enable_table_format &&
531 metrics.is_uniform_array &&
532 metrics.table_row_width + 1 + row_indent_width <= opts.max_total_line_length) {
533 return layout_mode::table;
534 }
535
536 return layout_mode::expanded;
537}
538
539//
540// Fractured Formatter Implementation
541//
542
543inline fractured_formatter::fractured_formatter(const fractured_json_options& opts)
544 : options_(opts) {}
545
546simdjson_inline void fractured_formatter::print_newline() {
547 if (current_layout_ == layout_mode::single_line) {
548 return; // No newlines in inline mode
549 }
550 one_char('\n');
551 current_line_length_ = 0;
552}
553
554simdjson_inline void fractured_formatter::print_indents(size_t depth) {
555 if (current_layout_ == layout_mode::single_line) {
556 return; // No indentation in inline mode
557 }
558 for (size_t i = 0; i < depth * options_.indent_spaces; i++) {
559 one_char(' ');
560 current_line_length_++;
561 }
562}
563
564simdjson_inline void fractured_formatter::print_space() {
565 one_char(' ');
566 current_line_length_++;
567}
568
569inline void fractured_formatter::set_layout_mode(layout_mode mode) {
570 current_layout_ = mode;
571}
572
573inline layout_mode fractured_formatter::get_layout_mode() const {
574 return current_layout_;
575}
576
577inline void fractured_formatter::track_line_length(size_t chars) {
578 current_line_length_ += chars;
579}
580
581inline bool fractured_formatter::should_break_line(size_t upcoming_length) const {
582 return (current_line_length_ + upcoming_length) > options_.max_total_line_length;
583}
584
585inline const fractured_json_options& fractured_formatter::options() const {
586 return options_;
587}
588
589//
590// Fractured String Builder Implementation
591//
592
593inline fractured_string_builder::fractured_string_builder(const fractured_json_options& opts)
594 : format_(opts), analyzer_{}, options_(opts) {}
595
596inline void fractured_string_builder::append(const dom::element& value) {
597 // Phase 1: Analyze structure (metrics tree is built recursively)
598 element_metrics root_metrics = analyzer_.analyze(value, options_);
599
600 // Phase 2: Format using metrics tree (passed through recursion)
601 format_element(value, root_metrics, 0);
602}
603
604inline void fractured_string_builder::append(const dom::array& value) {
605 // Analyze the array to get proper metrics with children
606 element_metrics metrics = analyzer_.analyze_array(value, options_);
607 format_array(value, metrics, 0);
608}
609
610inline void fractured_string_builder::append(const dom::object& value) {
611 // Analyze the object to get proper metrics with children
612 element_metrics metrics = analyzer_.analyze_object(value, options_);
613 format_object(value, metrics, 0);
614}
615
616simdjson_inline void fractured_string_builder::clear() {
617 format_.clear();
618 analyzer_.clear();
619}
620
621simdjson_inline std::string_view fractured_string_builder::str() const {
622 return format_.str();
623}
624
625inline void fractured_string_builder::format_element(const dom::element& elem,
626 const element_metrics& metrics,
627 size_t depth,
628 bool has_trailing_comma) {
629 switch (elem.type()) {
630 case dom::element_type::ARRAY: {
631 dom::array arr;
632 if (elem.get_array().get(arr) == SUCCESS) {
633 format_array(arr, metrics, depth, has_trailing_comma);
634 }
635 break;
636 }
637 case dom::element_type::OBJECT: {
638 dom::object obj;
639 if (elem.get_object().get(obj) == SUCCESS) {
640 format_object(obj, metrics, depth, has_trailing_comma);
641 }
642 break;
643 }
644 default:
645 format_scalar(elem);
646 break;
647 }
648}
649
650inline void fractured_string_builder::format_array(const dom::array& arr,
651 const element_metrics& metrics,
652 size_t depth,
653 bool has_trailing_comma) {
654 layout_mode layout = structure_analyzer::decide_layout(metrics, depth, options_, has_trailing_comma);
655 switch (layout) {
656 case layout_mode::single_line:
657 format_array_inline(arr, metrics);
658 break;
659 case layout_mode::compact_multiline:
660 format_array_compact_multiline(arr, metrics, depth);
661 break;
662 case layout_mode::table:
663 format_array_as_table(arr, metrics, depth);
664 break;
665 case layout_mode::expanded:
666 default:
667 format_array_expanded(arr, metrics, depth);
668 break;
669 }
670}
671
672inline void fractured_string_builder::format_array_inline(const dom::array& arr,
673 const element_metrics& metrics) {
674 scoped_single_line_mode single_line(format_);
675
676 format_.start_array();
677
678 bool first = true;
679 bool empty = true;
680 size_t child_idx = 0;
681 for (dom::element elem : arr) {
682 empty = false;
683 if (!first) {
684 format_.comma();
685 if (options_.comma_padding) {
686 format_.print_space();
687 }
688 } else if (bracket_padding_for(metrics)) {
689 format_.print_space();
690 }
691 first = false;
692 const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx);
693 format_element(elem, child_metrics, 0);
694 child_idx++;
695 }
696
697 if (bracket_padding_for(metrics) && !empty) {
698 format_.print_space();
699 }
700 format_.end_array();
701}
702
703inline void fractured_string_builder::format_array_compact_multiline(const dom::array& arr,
704 const element_metrics& metrics,
705 size_t depth) {
706 if (metrics.is_uniform_array) {
707 format_array_compact_multiline_aligned(arr, metrics, depth);
708 return;
709 }
710
711 format_.start_array();
712 format_.print_newline();
713 format_.print_indents(depth + 1);
714
715 bool first = true;
716 bool prev_item_was_expanded = false;
717 size_t child_idx = 0;
718
719 for (dom::element elem : arr) {
720 const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx);
721
722 if (!first) {
723 format_.comma();
724 format_.track_line_length(1);
725
726 // Check if we should break to new line
727 if (prev_item_was_expanded ||
728 format_.should_break_line(child_metrics.estimated_inline_len)) {
729 format_.print_newline();
730 format_.print_indents(depth + 1);
731 } else if (options_.comma_padding) {
732 format_.print_space();
733 }
734 }
735 first = false;
736
737 bool is_last = (child_idx + 1 == metrics.child_count);
738 layout_mode item_layout = structure_analyzer::decide_layout(child_metrics, depth + 1, options_, !is_last);
739 bool item_fits = item_layout == layout_mode::single_line;
740 if (item_fits) {
741 {
742 scoped_single_line_mode single_line(format_);
743 format_element(elem, child_metrics, depth + 1, !is_last);
744 }
745 format_.track_line_length(child_metrics.estimated_inline_len);
746 } else {
747 format_element(elem, child_metrics, depth + 1, !is_last);
748 }
749 prev_item_was_expanded = !item_fits;
750
751 child_idx++;
752 }
753
754 format_.print_newline();
755 format_.print_indents(depth);
756 format_.end_array();
757}
758
759inline void fractured_string_builder::format_array_compact_multiline_aligned(
760 const dom::array& arr, const element_metrics& metrics, size_t depth) {
761 const std::vector<table_column>& columns = metrics.table_columns;
762
763 format_.start_array();
764 format_.print_newline();
765 format_.print_indents(depth + 1);
766
767 size_t indent_width = (depth + 1) * options_.indent_spaces;
768 size_t available_line_space = (indent_width >= options_.max_total_line_length)
769 ? 0 : options_.max_total_line_length - indent_width;
770 size_t comma_width = options_.comma_padding ? 2 : 1;
771 size_t remaining_line_space = available_line_space;
772
773 bool first = true;
774 size_t child_idx = 0;
775
776 for (dom::element elem : arr) {
777 bool needs_comma = (child_idx + 1 < metrics.child_count);
778 size_t space_needed = metrics.table_row_width_full + (needs_comma ? comma_width : 0);
779
780 if (!first) {
781 if (remaining_line_space < space_needed) {
782 format_.print_newline();
783 format_.print_indents(depth + 1);
784 remaining_line_space = available_line_space;
785 } else if (options_.comma_padding) {
786 format_.print_space();
787 }
788 }
789 first = false;
790
791 const element_metrics& row_metrics = child_metrics_at(metrics.children, child_idx);
792 if (columns.empty()) {
793 format_table_scalar_row(elem, row_metrics, metrics.table_row_width_full, depth + 1,
794 metrics.scalar_column_type);
795 } else {
796 format_table_row(elem, row_metrics, columns, depth + 1);
797 }
798 if (needs_comma) {
799 format_.comma();
800 }
801 remaining_line_space -= (std::min)(remaining_line_space, space_needed);
802 child_idx++;
803 }
804
805 format_.print_newline();
806 format_.print_indents(depth);
807 format_.end_array();
808}
809
810inline void fractured_string_builder::format_table_row_columns(
811 const std::vector<table_column>& columns,
812 const std::vector<bool>& found,
813 const std::vector<dom::element>& values,
814 const std::vector<const element_metrics*>& value_metrics,
815 size_t depth) {
816 const size_t num_columns = columns.size();
817 size_t last_present_idx = num_columns;
818 for (size_t i = 0; i < num_columns; i++) {
819 if (found[i]) last_present_idx = i;
820 }
821
822 size_t comma_width = options_.comma_padding ? 2 : 1;
823
824 for (size_t col_idx = 0; col_idx < num_columns; col_idx++) {
825 const table_column& column = columns[col_idx];
826 const bool is_last_col = (col_idx == num_columns - 1);
827
828 if (found[col_idx]) {
829 if (!column.key.empty()) {
830 format_.key(column.key);
831 if (options_.colon_padding) {
832 format_.print_space();
833 }
834 }
835
836 bool needs_comma = !is_last_col && (col_idx < last_present_idx);
837
838 if (!column.children.empty()) {
839 // Recurses into this cell's own columns instead of a plain value;
840 // every row aligns those the same way (blank-padding missing
841 // ones), so the result is always exactly column.width wide
842 // no padding needed afterward, unlike the leaf case below.
843 if (column.type == table_column_type::object) {
844 dom::object sub_obj;
845 if (values[col_idx].get_object().get(sub_obj) == SUCCESS) {
846 const element_metrics& sub_metrics = child_metrics_at(value_metrics[col_idx]);
847 format_table_object_row(sub_obj, sub_metrics, column.children, depth);
848 }
849 } else {
850 dom::array sub_arr;
851 if (values[col_idx].get_array().get(sub_arr) == SUCCESS) {
852 const element_metrics& sub_metrics = child_metrics_at(value_metrics[col_idx]);
853 format_table_array_row(sub_arr, sub_metrics, column.children, depth);
854 }
855 }
856 // value is already padded
857 if (needs_comma) {
858 format_.comma();
859 if (options_.comma_padding) {
860 format_.print_space();
861 }
862 }
863 } else {
864 const element_metrics& vm = child_metrics_at(value_metrics[col_idx]);
865 format_table_leaf_value(values[col_idx], vm, column.width, column.type, needs_comma,
866 /*add_comma_space=*/true, depth);
867 }
868
869 if (!is_last_col && !needs_comma) {
870 // Found, but no more real values follow: blank space where a comma would go.
871 for (size_t i = 0; i < comma_width; i++) {
872 format_.one_char(' ');
873 }
874 }
875 } else {
876 size_t slot_width = column.width;
877 if (!column.key.empty()) {
878 slot_width += column.key_width + (options_.colon_padding ? 2 : 1);
879 }
880 for (size_t i = 0; i < slot_width; i++) {
881 format_.one_char(' ');
882 }
883
884 if (!is_last_col) {
885 for (size_t i = 0; i < comma_width; i++) {
886 format_.one_char(' ');
887 }
888 }
889 }
890 }
891}
892
893inline void fractured_string_builder::format_table_object_row(
894 const dom::object& obj, const element_metrics& row_metrics,
895 const std::vector<table_column>& columns, size_t depth) {
896 const size_t num_columns = columns.size();
897 std::vector<bool> found(num_columns, false);
898 std::vector<dom::element> values(num_columns);
899 std::vector<const element_metrics*> value_metrics(num_columns, nullptr);
900
901 for (size_t col_idx = 0; col_idx < num_columns; col_idx++) {
902 size_t field_idx = 0;
903 for (dom::key_value_pair field : obj) {
904 if (field.key == columns[col_idx].key) {
905 found[col_idx] = true;
906 values[col_idx] = field.value;
907 value_metrics[col_idx] = (field_idx < row_metrics.children.size())
908 ? &row_metrics.children[field_idx] : nullptr;
909 break;
910 }
911 field_idx++;
912 }
913 }
914
915 bool nested = table_row_is_nested(columns);
916 format_.start_object();
917 if (nested ? options_.nested_bracket_padding : options_.simple_bracket_padding) {
918 format_.print_space();
919 }
920 format_table_row_columns(columns, found, values, value_metrics, depth);
921 if (nested ? options_.nested_bracket_padding : options_.simple_bracket_padding) {
922 format_.print_space();
923 }
924 format_.end_object();
925}
926
927inline void fractured_string_builder::format_table_array_row(
928 const dom::array& arr, const element_metrics& row_metrics,
929 const std::vector<table_column>& columns, size_t depth) {
930 const size_t num_columns = columns.size();
931 std::vector<bool> found(num_columns, false);
932 std::vector<dom::element> values(num_columns);
933 std::vector<const element_metrics*> value_metrics(num_columns, nullptr);
934
935 size_t idx = 0;
936 for (dom::element item : arr) {
937 if (idx >= num_columns) break;
938 found[idx] = true;
939 values[idx] = item;
940 value_metrics[idx] = (idx < row_metrics.children.size()) ? &row_metrics.children[idx] : nullptr;
941 idx++;
942 }
943
944 bool nested = table_row_is_nested(columns);
945 format_.start_array();
946 if (nested ? options_.nested_bracket_padding : options_.simple_bracket_padding) {
947 format_.print_space();
948 }
949 format_table_row_columns(columns, found, values, value_metrics, depth);
950 if (nested ? options_.nested_bracket_padding : options_.simple_bracket_padding) {
951 format_.print_space();
952 }
953 format_.end_array();
954}
955
956inline void fractured_string_builder::format_table_row(
957 const dom::element& elem, const element_metrics& row_metrics,
958 const std::vector<table_column>& columns, size_t depth) {
959 if (elem.type() == dom::element_type::ARRAY) {
960 dom::array arr;
961 if (elem.get_array().get(arr) == SUCCESS) {
962 format_table_array_row(arr, row_metrics, columns, depth);
963 }
964 } else {
965 dom::object obj;
966 if (elem.get_object().get(obj) == SUCCESS) {
967 format_table_object_row(obj, row_metrics, columns, depth);
968 }
969 }
970}
971
972inline bool fractured_string_builder::comma_goes_before_padding(table_column_type column_type) const {
973 switch (options_.comma_placement) {
974 case table_comma_placement::before_padding: return true;
975 case table_comma_placement::after_padding: return false;
976 case table_comma_placement::before_padding_except_numbers:
977 default:
978 return column_type != table_column_type::number;
979 }
980}
981
982inline void fractured_string_builder::format_table_leaf_value(
983 const dom::element& elem, const element_metrics& vm, size_t width,
984 table_column_type column_type, bool needs_comma, bool add_comma_space, size_t depth) {
985 bool comma_before_pad = needs_comma && comma_goes_before_padding(column_type);
986 bool comma_after_pad = needs_comma && !comma_before_pad;
987
988 bool right_align = column_type == table_column_type::number &&
989 options_.number_alignment == number_list_alignment::right;
990
991 size_t value_len = vm.estimated_inline_len;
992 size_t left_pad = 0;
993 size_t right_pad = 0;
994 if (right_align) {
995 left_pad = (width > value_len) ? width - value_len : 0;
996 comma_before_pad = needs_comma;
997 comma_after_pad = false;
998 } else {
999 right_pad = (width > value_len) ? width - value_len : 0;
1000 }
1001
1002 for (size_t i = 0; i < left_pad; i++) {
1003 format_.one_char(' ');
1004 }
1005
1006 {
1007 scoped_single_line_mode single_line(format_);
1008 format_element(elem, vm, depth);
1009 }
1010
1011 if (comma_before_pad) {
1012 format_.comma();
1013 }
1014 for (size_t i = 0; i < right_pad; i++) {
1015 format_.one_char(' ');
1016 }
1017 if (comma_after_pad) {
1018 format_.comma();
1019 }
1020 if (needs_comma && add_comma_space && options_.comma_padding) {
1021 format_.print_space();
1022 }
1023}
1024
1025inline void fractured_string_builder::format_table_scalar_row(
1026 const dom::element& elem, const element_metrics& row_metrics, size_t width, size_t depth,
1027 table_column_type column_type) {
1028 format_table_leaf_value(elem, row_metrics, width, column_type,
1029 /*needs_comma=*/false, /*add_comma_space=*/false, depth);
1030}
1031
1032inline void fractured_string_builder::format_array_as_table(const dom::array& arr,
1033 const element_metrics& metrics,
1034 size_t depth) {
1035 if (!metrics.is_uniform_array) {
1036 format_array_expanded(arr, metrics, depth);
1037 return;
1038 }
1039 const std::vector<table_column>& columns = metrics.table_columns;
1040
1041 format_.start_array();
1042 format_.print_newline();
1043
1044 bool first_row = true;
1045 size_t child_idx = 0;
1046 for (dom::element elem : arr) {
1047 if (!first_row) {
1048 format_.comma();
1049 format_.print_newline();
1050 }
1051 first_row = false;
1052
1053 format_.print_indents(depth + 1);
1054
1055 const element_metrics& row_metrics = child_metrics_at(metrics.children, child_idx);
1056 if (columns.empty()) {
1057 format_table_scalar_row(elem, row_metrics, metrics.table_row_width, depth + 1,
1058 metrics.scalar_column_type);
1059 } else {
1060 format_table_row(elem, row_metrics, columns, depth + 1);
1061 }
1062 child_idx++;
1063 }
1064
1065 format_.print_newline();
1066 format_.print_indents(depth);
1067 format_.end_array();
1068}
1069
1070inline void fractured_string_builder::format_array_expanded(const dom::array& arr,
1071 const element_metrics& metrics,
1072 size_t depth) {
1073 format_.start_array();
1074
1075 bool empty = true;
1076 bool first = true;
1077 size_t child_idx = 0;
1078
1079 for (dom::element elem : arr) {
1080 empty = false;
1081 if (!first) {
1082 format_.comma();
1083 }
1084 first = false;
1085
1086 format_.print_newline();
1087 format_.print_indents(depth + 1);
1088 const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx);
1089 bool is_last = (child_idx + 1 == metrics.child_count);
1090 format_element(elem, child_metrics, depth + 1, !is_last);
1091 child_idx++;
1092 }
1093
1094 if (!empty) {
1095 format_.print_newline();
1096 format_.print_indents(depth);
1097 }
1098 format_.end_array();
1099}
1100
1101inline void fractured_string_builder::format_object(const dom::object& obj,
1102 const element_metrics& metrics,
1103 size_t depth,
1104 bool has_trailing_comma) {
1105 layout_mode layout = structure_analyzer::decide_layout(metrics, depth, options_, has_trailing_comma);
1106 if (layout == layout_mode::single_line) {
1107 format_object_inline(obj, metrics);
1108 } else {
1109 format_object_expanded(obj, metrics, depth);
1110 }
1111}
1112
1113inline void fractured_string_builder::format_object_inline(const dom::object& obj,
1114 const element_metrics& metrics) {
1115 scoped_single_line_mode single_line(format_);
1116
1117 format_.start_object();
1118
1119 bool empty = true;
1120 bool first = true;
1121 size_t child_idx = 0;
1122
1123 for (dom::key_value_pair field : obj) {
1124 empty = false;
1125 if (!first) {
1126 format_.comma();
1127 if (options_.comma_padding) {
1128 format_.print_space();
1129 }
1130 } else if (bracket_padding_for(metrics)) {
1131 format_.print_space();
1132 }
1133 first = false;
1134
1135 format_.key(field.key);
1136 if (options_.colon_padding) {
1137 format_.print_space();
1138 }
1139 const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx);
1140 format_element(field.value, child_metrics, 0);
1141 child_idx++;
1142 }
1143
1144 if (bracket_padding_for(metrics) && !empty) {
1145 format_.print_space();
1146 }
1147 format_.end_object();
1148}
1149
1150inline void fractured_string_builder::format_object_expanded(const dom::object& obj,
1151 const element_metrics& metrics,
1152 size_t depth) {
1153 format_.start_object();
1154
1155 bool empty = true;
1156 bool first = true;
1157 size_t child_idx = 0;
1158
1159 for (dom::key_value_pair field : obj) {
1160 empty = false;
1161 if (!first) {
1162 format_.comma();
1163 }
1164 first = false;
1165
1166 format_.print_newline();
1167 format_.print_indents(depth + 1);
1168 format_.key(field.key);
1169 if (options_.colon_padding) {
1170 format_.print_space();
1171 }
1172 const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx);
1173 bool is_last = (child_idx + 1 == metrics.child_count);
1174 format_element(field.value, child_metrics, depth + 1, !is_last);
1175 child_idx++;
1176 }
1177
1178 if (!empty) {
1179 format_.print_newline();
1180 format_.print_indents(depth);
1181 }
1182 format_.end_object();
1183}
1184
1185inline void fractured_string_builder::format_scalar(const dom::element& elem) {
1186 switch (elem.type()) {
1187 case dom::element_type::STRING: {
1188 std::string_view str;
1189 if (elem.get_string().get(str) == SUCCESS) {
1190 format_.string(str);
1191 }
1192 break;
1193 }
1194 case dom::element_type::INT64: {
1195 int64_t val;
1196 if (elem.get_int64().get(val) == SUCCESS) {
1197 format_.number(val);
1198 }
1199 break;
1200 }
1201 case dom::element_type::UINT64: {
1202 uint64_t val;
1203 if (elem.get_uint64().get(val) == SUCCESS) {
1204 format_.number(val);
1205 }
1206 break;
1207 }
1208 case dom::element_type::DOUBLE: {
1209 double val;
1210 if (elem.get_double().get(val) == SUCCESS) {
1211 format_.number(val);
1212 }
1213 break;
1214 }
1215 case dom::element_type::BOOL: {
1216 bool val;
1217 if (elem.get_bool().get(val) == SUCCESS) {
1218 val ? format_.true_atom() : format_.false_atom();
1219 }
1220 break;
1221 }
1222 case dom::element_type::NULL_VALUE:
1223 format_.null_atom();
1224 break;
1225 default:
1226 break;
1227 }
1228}
1229
1230inline bool fractured_string_builder::bracket_padding_for(const element_metrics& metrics) const {
1231 return metrics.complexity >= 2 ? options_.nested_bracket_padding : options_.simple_bracket_padding;
1232}
1233
1234} // namespace internal
1235
1236//
1237// Public API Implementation
1238//
1239
1240template <class T>
1241std::string fractured_json(T x) {
1243}
1244
1245template <class T>
1246std::string fractured_json(T x, const fractured_json_options& options) {
1247 internal::fractured_string_builder sb(options);
1248 sb.append(x);
1249 std::string_view result = sb.str();
1250 return std::string(result.data(), result.size());
1251}
1252
1253#if SIMDJSON_EXCEPTIONS
1254template <class T>
1255std::string fractured_json(simdjson_result<T> x) {
1256 if (x.error()) {
1257 throw simdjson_error(x.error());
1258 }
1259 return fractured_json(x.value());
1260}
1261
1262template <class T>
1263std::string fractured_json(simdjson_result<T> x, const fractured_json_options& options) {
1264 if (x.error()) {
1265 throw simdjson_error(x.error());
1266 }
1267 return fractured_json(x.value(), options);
1268}
1269#endif
1270
1271//
1272// String-based API for formatting any JSON string
1273//
1274
1275inline std::string fractured_json_string(std::string_view json_str) {
1277}
1278
1279inline std::string fractured_json_string(std::string_view json_str,
1280 const fractured_json_options& options) {
1281 // Parse the JSON string
1282 dom::parser parser;
1283 dom::element doc;
1284 // Need to pad the string for simdjson
1285 auto padded = padded_string(json_str);
1286 auto error = parser.parse(padded).get(doc);
1287 if (error) {
1288 // If parsing fails, return the original string
1289 return std::string(json_str);
1290 }
1291 return fractured_json(doc, options);
1292}
1293
1294} // namespace simdjson
1295
1296#endif // SIMDJSON_DOM_FRACTURED_JSON_INL_H
A JSON element.
Definition element.h:36
A persistent document parser.
Definition parser.h:30
simdjson_result< element > parse(const uint8_t *buf, size_t len, bool realloc_if_needed=true) &noexcept
Parse a JSON document and return a temporary reference to it.
Definition parser-inl.h:178
element_type
The actual concrete type of a JSON element This is the type it is most easily cast to with get<>.
Definition element.h:16
@ STRING
std::string_view
@ UINT64
uint64_t: any integer that fits in uint64_t but not int64_t
@ DOUBLE
double: Any number with a "." or "e" that fits in double.
The top level simdjson namespace, containing everything the library provides.
Definition base.h:8
std::string fractured_json_string(std::string_view json_str)
Format a JSON string using FracturedJson formatting.
std::string fractured_json(T x)
Format JSON using FracturedJson formatting with default options.
@ SUCCESS
No error.
Definition error.h:20
Configuration options for FracturedJson formatting.
String with extra allocation for ease of use with parser::parse()