POC
PHP
Bootstrap 5
What it does
Scans any JSON tree and auto-builds tables for detected “array of objects”. Each table can be downloaded as CSV. A tolerant JSON loader removes UTF-8 BOM and trailing control chars (incl. 0x1A
/^Z
).
Usage
?file=pedido.json
– open a JSON file?file=pedido.json&root=Mensaje.Body.Pedidos.Detalles
– focus a subtree via dot-path- Click “Download CSV” on any table to export
Tip: End the JSON with
}
or ]
. If your writer adds ^Z
(0x1A
), the loader strips it.
Features
- Auto table detection (array-of-objects)
- CSV export per table
root=
dot-path focus- UTF-8 BOM / trailing control-char tolerant
- Zero dependencies besides Bootstrap CSS (for the demo UI)
<?php
declare(strict_types=1);
// Universal JSON Reader (lite) – render JSON as tables + CSV
// Usage examples:
// universal_tables.php?file=data.json
// universal_tables.php?file=data.json&root=Mensaje.Body.Pedidos.Detalles
// ---- Load & decode (BOM + trailing control chars tolerant) ----
$file = $_GET['file'] ?? 'data.json';
if (!is_file($file)) { http_response_code(404); exit('File not found: ' . $file); }
$raw = file_get_contents($file);
// strip UTF-8 BOM
$raw = preg_replace('/^\xEF\xBB\xBF/', '', $raw);
// strip trailing control chars (incl. 0x1A/^Z)
$raw = rtrim($raw, "\x00..\x1F\x7F");
$data = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING | JSON_INVALID_UTF8_SUBSTITUTE);
if (json_last_error() !== JSON_ERROR_NONE) exit('JSON error: ' . json_last_error_msg());
// Optional: focus on subtree via dot path
if (!empty($_GET['root'])) {
$node = pick_dot_path($data, (string)$_GET['root']);
if ($node !== null) $data = $node;
}
// ---- Build tables ----
$tables = [];
scan_to_tables($data, 'root', [], $tables);
// CSV endpoint
if (isset($_GET['csv'])) {
$k = (string)$_GET['csv'];
if (!isset($tables[$k])) exit('Unknown table key');
[$cols, $rows] = [$tables[$k]['cols'], $tables[$k]['rows']];
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="'.sanitize($k).'.csv"');
$out = fopen('php://output', 'w');
fputcsv($out, $cols);
foreach ($rows as $r) fputcsv($out, array_map('to_scalar', $r));
fclose($out);
exit;
}
// ---- Render minimal HTML ----
?><!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Universal JSON Tables – <?=htmlspecialchars($file)?></title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body{font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif;background:#f7f8fb}
.container{max-width:1200px;margin:24px auto 56px;padding:0 16px}
.tbl{margin:18px 0}
.scroll{overflow:auto;border:1px solid #e4e7ef;background:#fff;border-radius:10px}
thead th{position:sticky;top:0;background:#eef2f9}
</style></head><body>
<div class="container">
<h1 class="h4">Universal JSON Tables</h1>
<div class="text-muted mb-2">File: <strong><?=htmlspecialchars($file)?></strong> • Tables: <span class="badge bg-secondary"><?=count($tables)?></span></div>
<?php foreach ($tables as $key => $tab): ?>
<section class="tbl" id="<?=htmlspecialchars($key)?>">
<h2 class="h6 d-flex align-items-center gap-2">
<span><?=htmlspecialchars($key)?></span>
<a class="btn btn-sm btn-outline-primary" href="?file=<?=rawurlencode($file)?>&csv=<?=rawurlencode($key)?>">Download CSV</a>
<span class="badge bg-secondary"><?=count($tab['cols'])?> cols</span>
<span class="badge bg-secondary"><?=count($tab['rows'])?> rows</span>
</h2>
<div class="scroll">
<table class="table table-sm mb-0">
<thead><tr>
<?php foreach ($tab['cols'] as $c): ?><th><?=htmlspecialchars($c)?></th><?php endforeach; ?>
</tr></thead>
<tbody>
<?php foreach ($tab['rows'] as $r): ?>
<tr>
<?php foreach ($tab['cols'] as $i => $_): $cell = $r[$i] ?? ''; ?>
<td><?=htmlspecialchars(to_scalar($cell))?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endforeach; ?>
<?php if (!$tables): ?>
<p class="text-muted">No array-of-objects found. Use <code>root</code> to focus on a subtree.</p>
<?php endif; ?>
</div>
</body></html>
<?php
// ----------------- Helpers -----------------
function scan_to_tables($x, string $path, array $ctx, array &$tables): void {
if (is_array($x) && is_assoc($x)) {
foreach ($x as $k => $v) {
$child = $path.'.'.$k;
if (is_array($v)) {
if (is_array_of_objects($v)) build_from_aoo($v, $child, $ctx, $tables);
else build_from_scalars($v, $child, $ctx, $tables);
} else {
$ctx[$child] = $v;
}
}
} elseif (is_array($x)) {
if (is_array_of_objects($x)) build_from_aoo($x, $path, $ctx, $tables);
else build_from_scalars($x, $path, $ctx, $tables);
}
}
function build_from_aoo(array $arr, string $name, array $ctx, array &$tables): void {
$cols = merge_cols(array_keys($ctx), keys_union($arr));
$tab = &ensure_table($tables, $name);
$tab['cols'] = merge_cols($tab['cols'], $cols);
foreach ($arr as $obj) {
$row = array_fill(0, count($tab['cols']), '');
foreach ($tab['cols'] as $i => $c) if (array_key_exists($c, $ctx)) $row[$i] = $ctx[$c];
if (is_array($obj) && is_assoc($obj)) {
foreach ($obj as $k => $v) {
$idx = array_search($k, $tab['cols'], true);
if ($idx !== false && $row[$idx] === '') $row[$idx] = $v;
}
}
foreach ($row as $i => $v) $row[$i] = to_cell($v);
$tab['rows'][] = $row;
}
}
function build_from_scalars(array $arr, string $name, array $ctx, array &$tables): void {
$tab = &ensure_table($tables, $name);
$one = $name.'[]';
$tab['cols'] = merge_cols($tab['cols'], array_merge(array_keys($ctx), [$one]));
foreach ($arr as $v) {
$row = array_fill(0, count($tab['cols']), '');
foreach ($tab['cols'] as $i => $c) if (array_key_exists($c, $ctx)) $row[$i] = to_cell($ctx[$c]);
$idx = array_search($one, $tab['cols'], true);
if ($idx !== false) $row[$idx] = to_cell($v);
$tab['rows'][] = $row;
}
}
function &ensure_table(array &$tables, string $name): array {
if (!isset($tables[$name])) $tables[$name] = ['cols'=>[], 'rows'=>[]];
return $tables[$name];
}
function is_assoc(array $a): bool { foreach (array_keys($a) as $k) if (is_string($k)) return true; return false; }
function is_array_of_objects(array $a): bool { if ($a===[]) return false; foreach ($a as $v) if (is_array($v) && is_assoc($v)) return true; return false; }
function keys_union(array $arr): array { $seen=[]; foreach ($arr as $o) if (is_array($o)&&is_assoc($o)) foreach ($o as $k=>$_) $seen[$k]=1; return array_keys($seen); }
function merge_cols(array $old, array $new): array { $m=[]; foreach ($old as $c) $m[$c]=1; $out=$old; foreach ($new as $c) if (!isset($m[$c])) {$m[$c]=1;$out[]=$c;} return $out; }
function to_cell($v): string {
if ($v===null) return '';
if (is_string($v)) return $v;
if (is_int($v)||is_float($v)) return (string)$v;
if (is_bool($v)) return $v?'true':'false';
if (is_array($v)) return json_encode($v, JSON_UNESCAPED_UNICODE);
return (string)$v;
}
function to_scalar($v): string { return to_cell($v); }
function sanitize(string $s): string { return preg_replace('/[^A-Za-z0-9._-]/','_', $s); }
function pick_dot_path($node, string $dot) {
foreach (explode('.', $dot) as $seg) {
if ($seg==='') continue;
if (is_array($node) && array_key_exists($seg, $node)) $node=$node[$seg]; else return null;
}
return $node;
}
?>
{
"Mensaje": {
"Body": {
"Pedidos": {
"Detalles": [
{ "pedaux": 1, "pedcpr": "0007019", "pedctd": 12 },
{ "pedaux": 2, "pedcpr": "0007020", "pedctd": 8 }
]
}
}
}
}