From 7034d6067fd2075253502959fe150fea7d21577a Mon Sep 17 00:00:00 2001 From: James Tanner Date: Sun, 9 Jul 2023 12:31:26 -0700 Subject: [PATCH] Implement path flattening in json exports to make them more readable --- ui/core/components/exporters.ts | 18 +++++++++++++++-- ui/core/utils.ts | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/ui/core/components/exporters.ts b/ui/core/components/exporters.ts index 7e72126262..31aae8aed5 100644 --- a/ui/core/components/exporters.ts +++ b/ui/core/components/exporters.ts @@ -9,7 +9,7 @@ import { IndividualSimSettings } from '../proto/ui'; import { classNames, raceNames } from '../proto_utils/names'; import { UnitStat } from '../proto_utils/stats'; import { specNames } from '../proto_utils/utils'; -import { downloadString } from '../utils'; +import { downloadString, jsonStringifyWithFlattenedPaths } from '../utils'; import { BaseModal } from './base_modal'; import { IndividualWowheadGearPlannerImporter } from './importers'; import { RaidSimRequest } from '../proto/api'; @@ -97,7 +97,21 @@ export class IndividualJsonExporter extends Exporter { } getData(): string { - return JSON.stringify(IndividualSimSettings.toJson(this.simUI.toProto()), null, 2); + return jsonStringifyWithFlattenedPaths(IndividualSimSettings.toJson(this.simUI.toProto()), 2, (value, path) => { + if (['stats', 'pseudoStats'].includes(path[path.length - 1])) { + return true; + } + + if (['player', 'equipment', 'items'].every((v, i) => path[i] == v)) { + return path.length > 3; + } + + if (path[0] == 'player' && path[1] == 'rotation' && ['prepullActions', 'priorityList'].includes(path[2])) { + return path[path.length - 2] == 'action'; + } + + return false; + }); } } diff --git a/ui/core/utils.ts b/ui/core/utils.ts index 58752243a3..896f2f40b7 100644 --- a/ui/core/utils.ts +++ b/ui/core/utils.ts @@ -233,4 +233,39 @@ export function randomString(len?: number): string { str += randomStringChars[Math.floor(Math.random() * randomStringChars.length)]; } return str; +} + +// Allows replacement of stringified objects based on the key and path. +// If handler returns a string, that string is used. Otherwise, the normal JSON.stringify result is returned. +export function jsonStringifyCustom(value: any, indent: number, handler: (value: any, path: Array) => string|undefined|void): string { + const indentStr = ' '.repeat(indent); + return jsonStringifyCustomHelper(value, indentStr, [], handler); +} +function jsonStringifyCustomHelper(value: any, indentStr: string, path: Array, handler: (value: any, path: Array) => string|undefined|void): string { + const handlerResult = handler(value, path); + if (handlerResult != null) { + return handlerResult; + } + + if (!(value instanceof Object)) { + return JSON.stringify(value); + } else if (value instanceof Array) { + let str = '[\n'; + const lines = value.map((e, i) => `${indentStr.repeat(path.length+1)}${jsonStringifyCustomHelper(e, indentStr, path.slice().concat([i + '']), handler)}${i == value.length - 1 ? '' : ','}\n`); + str += lines.join(''); + str += indentStr.repeat(path.length) + ']'; + return str; + } else { // Object + let str = '{\n'; + const len = Object.keys(value).length; + const lines = Object.entries(value).map(([fieldKey, fieldValue], i) => `${indentStr.repeat(path.length+1)}"${fieldKey}": ${jsonStringifyCustomHelper(fieldValue, indentStr, path.slice().concat([fieldKey]), handler)}${i == len - 1 ? '' : ','}\n`); + str += lines.join(''); + str += indentStr.repeat(path.length) + '}'; + return str; + } +} + +// Pretty-prints the value in JSON form, but does not prettify (flattens) sub-values where handler returns true. +export function jsonStringifyWithFlattenedPaths(value: any, indent: number, handler: (value: any, path: Array) => boolean): string { + return jsonStringifyCustom(value, indent, (value, path) => handler(value, path) ? JSON.stringify(value) : undefined); } \ No newline at end of file