104 lines
3.5 KiB
Plaintext
104 lines
3.5 KiB
Plaintext
/**
|
|
* JSON 导入导出(1.8.5)。
|
|
*
|
|
* 导出行对象数组:[{"id":"1","name":"alice"}, …]。
|
|
* 导入要求为对象数组;缺列按空值补齐,并检查列集合一致性(不一致记为警告)。
|
|
* 解析失败返回错误而非抛异常。
|
|
*/
|
|
export interface JsonParseResult {
|
|
header: string[];
|
|
rows: string[][];
|
|
errors: string[];
|
|
warnings: string[];
|
|
}
|
|
|
|
export class JsonCodec {
|
|
static serialize(header: string[], rows: string[][]): string {
|
|
const objects: Map<string, string>[] = [];
|
|
for (let i = 0; i < rows.length; i++) {
|
|
const obj: Map<string, string> = new Map<string, string>();
|
|
for (let j = 0; j < header.length; j++) {
|
|
const value: string | undefined = rows[i][j];
|
|
obj.set(header[j], value === undefined ? '' : value);
|
|
}
|
|
objects.push(obj);
|
|
}
|
|
const plain: Record<string, string>[] = [];
|
|
for (let i = 0; i < objects.length; i++) {
|
|
const record: Record<string, string> = {};
|
|
objects[i].forEach((value: string, key: string) => {
|
|
record[key] = value;
|
|
});
|
|
plain.push(record);
|
|
}
|
|
return JSON.stringify(plain, null, 2);
|
|
}
|
|
|
|
static parse(text: string): JsonParseResult {
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
let parsed: Object | null = null;
|
|
try {
|
|
parsed = JSON.parse(text) as Object;
|
|
} catch (e) {
|
|
const message: string = e instanceof Error ? e.message : String(e);
|
|
return { header: [], rows: [], errors: ['invalid JSON: ' + message], warnings: [] };
|
|
}
|
|
if (!Array.isArray(parsed)) {
|
|
return { header: [], rows: [], errors: ['top-level JSON value must be an array'], warnings: [] };
|
|
}
|
|
const array: Object[] = parsed as Object[];
|
|
if (array.length === 0) {
|
|
return { header: [], rows: [], errors: [], warnings: ['empty array'] };
|
|
}
|
|
const header: string[] = [];
|
|
const first: Object = array[0];
|
|
if (first === null || Array.isArray(first) || typeof first !== 'object') {
|
|
return { header: [], rows: [], errors: ['array items must be objects'], warnings: [] };
|
|
}
|
|
const firstRecord: Record<string, Object> = first as Record<string, Object>;
|
|
for (const key in firstRecord) {
|
|
header.push(key);
|
|
}
|
|
const rows: string[][] = [];
|
|
for (let i = 0; i < array.length; i++) {
|
|
const item: Object = array[i];
|
|
if (item === null || Array.isArray(item) || typeof item !== 'object') {
|
|
errors.push('item ' + i + ' is not an object');
|
|
continue;
|
|
}
|
|
const record: Record<string, Object> = item as Record<string, Object>;
|
|
const row: string[] = [];
|
|
for (let j = 0; j < header.length; j++) {
|
|
const value: Object = record[header[j]];
|
|
if (value === undefined) {
|
|
warnings.push('item ' + i + ' missing key ' + header[j]);
|
|
row.push('');
|
|
} else {
|
|
row.push(JsonCodec.stringifyValue(value));
|
|
}
|
|
}
|
|
for (const key in record) {
|
|
if (header.indexOf(key) < 0) {
|
|
warnings.push('item ' + i + ' has extra key ' + key + ' not in header');
|
|
}
|
|
}
|
|
rows.push(row);
|
|
}
|
|
return { header: header, rows: rows, errors: errors, warnings: warnings };
|
|
}
|
|
|
|
private static stringifyValue(value: Object): string {
|
|
if (value === null) {
|
|
return '';
|
|
}
|
|
if (typeof value === 'string') {
|
|
return value as string;
|
|
}
|
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
return String(value);
|
|
}
|
|
return JSON.stringify(value);
|
|
}
|
|
}
|