self::VERSION, 'reading_rule' => 'table_paths指定的节点使用columns和rows:每行各值按columns的同序字段对应。' . '这是完整原始记录的无损表示,不是摘要;所有行、日期、数值、原文、null和空项均保留。' . '表外对象照常读取;历史记录不能当成本次查体,空项不能当成患者否认或正常。', 'table_paths' => $paths, 'data' => $data, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_THROW_ON_ERROR); } /** Reversible verification helper. Table paths also disambiguate literal columns/rows objects. */ public static function decode(string $encoded): array { $wire = json_decode($encoded, true, 512, JSON_THROW_ON_ERROR); if (($wire['encoding'] ?? null) !== self::VERSION || !is_array($wire['table_paths'] ?? null) || !is_array($wire['data'] ?? null)) { throw new \InvalidArgumentException('Invalid clinical row encoding'); } $tables = []; foreach ($wire['table_paths'] as $path) { if (!is_array($path) || !array_is_list($path)) throw new \InvalidArgumentException('Invalid table path'); $tables[serialize($path)] = true; } return self::unpack($wire['data'], [], $tables); } private static function pack($node, array $path, array &$paths) { if (!is_array($node)) return $node; if (array_is_list($node) && count($node) > 1 && is_array($node[0]) && !array_is_list($node[0])) { $columns = array_keys($node[0]); $sameShape = true; foreach ($node as $row) { if (!is_array($row) || array_keys($row) !== $columns) { $sameShape = false; break; } } if ($sameShape) { $paths[] = $path; $rows = []; foreach ($node as $r => $row) { $values = []; foreach (array_values($row) as $c => $value) { $values[] = self::pack($value, [...$path, 'rows', $r, $c], $paths); } $rows[] = $values; } return ['columns' => $columns, 'rows' => $rows]; } } $result = []; foreach ($node as $key => $value) $result[$key] = self::pack($value, [...$path, $key], $paths); return $result; } private static function unpack($node, array $path, array $tables) { if (!is_array($node)) return $node; if (isset($tables[serialize($path)])) { if (!is_array($node['columns'] ?? null) || !is_array($node['rows'] ?? null)) { throw new \InvalidArgumentException('Invalid clinical row table'); } $result = []; foreach ($node['rows'] as $r => $row) { if (!is_array($row) || count($row) !== count($node['columns'])) { throw new \InvalidArgumentException('Invalid clinical row width'); } $values = []; foreach ($row as $c => $value) $values[] = self::unpack($value, [...$path, 'rows', $r, $c], $tables); $result[] = array_combine($node['columns'], $values); } return $result; } $result = []; foreach ($node as $key => $value) $result[$key] = self::unpack($value, [...$path, $key], $tables); return $result; } }