perf: consolidate array & header encoding functions

This commit is contained in:
Johann Schopplich
2025-10-23 14:31:24 +02:00
parent 425a34b6cd
commit 67491ab6b6
2 changed files with 46 additions and 81 deletions

View File

@@ -18,10 +18,7 @@ import {
import { import {
encodeKey, encodeKey,
encodePrimitive, encodePrimitive,
formatArrayHeader, formatHeader,
formatKeyedArrayHeader,
formatKeyedTableHeader,
formatTabularHeader,
joinEncodedValues, joinEncodedValues,
} from './primitives' } from './primitives'
import { LineWriter } from './writer' import { LineWriter } from './writer'
@@ -36,7 +33,7 @@ export function encodeValue(value: JsonValue, options: ResolvedEncodeOptions): s
const writer = new LineWriter(options.indent) const writer = new LineWriter(options.indent)
if (isJsonArray(value)) { if (isJsonArray(value)) {
encodeRootArray(value, writer, options) encodeArray(undefined, value, writer, 0, options)
} }
else if (isJsonObject(value)) { else if (isJsonObject(value)) {
encodeObject(value, writer, 0, options) encodeObject(value, writer, 0, options)
@@ -64,7 +61,7 @@ export function encodeKeyValuePair(key: string, value: JsonValue, writer: LineWr
writer.push(depth, `${encodedKey}: ${encodePrimitive(value, options.delimiter)}`) writer.push(depth, `${encodedKey}: ${encodePrimitive(value, options.delimiter)}`)
} }
else if (isJsonArray(value)) { else if (isJsonArray(value)) {
encodeArrayProperty(key, value, writer, depth, options) encodeArray(key, value, writer, depth, options)
} }
else if (isJsonObject(value)) { else if (isJsonObject(value)) {
const nestedKeys = Object.keys(value) const nestedKeys = Object.keys(value)
@@ -83,50 +80,24 @@ export function encodeKeyValuePair(key: string, value: JsonValue, writer: LineWr
// #region Array encoding // #region Array encoding
export function encodeRootArray(value: JsonArray, writer: LineWriter, options: ResolvedEncodeOptions): void { export function encodeArray(
key: string | undefined,
value: JsonArray,
writer: LineWriter,
depth: Depth,
options: ResolvedEncodeOptions,
): void {
if (value.length === 0) { if (value.length === 0) {
writer.push(0, '[0]:') if (key === undefined) {
return writer.push(depth, '[0]:')
}
// Primitive array
if (isArrayOfPrimitives(value)) {
encodeInlinePrimitiveArray(undefined, value, writer, 0, options)
return
}
// Array of arrays (all primitives)
if (isArrayOfArrays(value)) {
const allPrimitiveArrays = value.every(arr => isArrayOfPrimitives(arr))
if (allPrimitiveArrays) {
encodeArrayOfArraysAsListItems(undefined, value, writer, 0, options)
return
}
}
// Array of objects
if (isArrayOfObjects(value)) {
const header = detectTabularHeader(value)
if (header) {
encodeArrayOfObjectsAsTabular(undefined, value, header, writer, 0, options)
} }
else { else {
encodeArrayOfObjectsAsListItems(undefined, value, writer, 0, options) const encodedKey = encodeKey(key)
writer.push(depth, `${encodedKey}[0]:`)
} }
return return
} }
// Mixed array: fallback to expanded format (not in spec, but safe default)
encodeMixedArrayAsListItems(undefined, value, writer, 0, options)
}
export function encodeArrayProperty(key: string, value: JsonArray, writer: LineWriter, depth: Depth, options: ResolvedEncodeOptions): void {
if (value.length === 0) {
const encodedKey = encodeKey(key)
writer.push(depth, `${encodedKey}[0]:`)
return
}
// Primitive array // Primitive array
if (isArrayOfPrimitives(value)) { if (isArrayOfPrimitives(value)) {
encodeInlinePrimitiveArray(key, value, writer, depth, options) encodeInlinePrimitiveArray(key, value, writer, depth, options)
@@ -149,7 +120,7 @@ export function encodeArrayProperty(key: string, value: JsonArray, writer: LineW
encodeArrayOfObjectsAsTabular(key, value, header, writer, depth, options) encodeArrayOfObjectsAsTabular(key, value, header, writer, depth, options)
} }
else { else {
encodeArrayOfObjectsAsListItems(key, value, writer, depth, options) encodeMixedArrayAsListItems(key, value, writer, depth, options)
} }
return return
} }
@@ -169,7 +140,7 @@ export function encodeInlinePrimitiveArray(
depth: Depth, depth: Depth,
options: ResolvedEncodeOptions, options: ResolvedEncodeOptions,
): void { ): void {
const header = prefix ? formatKeyedArrayHeader(prefix, values.length) : formatArrayHeader(values.length) const header = formatHeader(values.length, prefix ? { key: prefix } : undefined)
const joinedValue = joinEncodedValues(values, options.delimiter) const joinedValue = joinEncodedValues(values, options.delimiter)
// Only add space if there are values // Only add space if there are values
if (values.length === 0) { if (values.length === 0) {
@@ -191,7 +162,7 @@ export function encodeArrayOfArraysAsListItems(
depth: Depth, depth: Depth,
options: ResolvedEncodeOptions, options: ResolvedEncodeOptions,
): void { ): void {
const header = prefix ? formatKeyedArrayHeader(prefix, values.length) : formatArrayHeader(values.length) const header = formatHeader(values.length, prefix ? { key: prefix } : undefined)
writer.push(depth, header) writer.push(depth, header)
for (const arr of values) { for (const arr of values) {
@@ -203,7 +174,7 @@ export function encodeArrayOfArraysAsListItems(
} }
export function formatInlineArray(values: readonly JsonPrimitive[], delimiter: string): string { export function formatInlineArray(values: readonly JsonPrimitive[], delimiter: string): string {
const header = formatArrayHeader(values.length) const header = formatHeader(values.length)
const joinedValue = joinEncodedValues(values, delimiter) const joinedValue = joinEncodedValues(values, delimiter)
// Only add space if there are values // Only add space if there are values
if (values.length === 0) { if (values.length === 0) {
@@ -224,9 +195,7 @@ export function encodeArrayOfObjectsAsTabular(
depth: Depth, depth: Depth,
options: ResolvedEncodeOptions, options: ResolvedEncodeOptions,
): void { ): void {
const headerStr = prefix const headerStr = formatHeader(rows.length, { key: prefix, fields: header })
? formatKeyedTableHeader(prefix, rows.length, header)
: formatTabularHeader(rows.length, header)
writer.push(depth, `${headerStr}`) writer.push(depth, `${headerStr}`)
for (const row of rows) { for (const row of rows) {
@@ -287,7 +256,7 @@ export function encodeMixedArrayAsListItems(
depth: Depth, depth: Depth,
options: ResolvedEncodeOptions, options: ResolvedEncodeOptions,
): void { ): void {
const header = prefix ? formatKeyedArrayHeader(prefix, items.length) : formatArrayHeader(items.length) const header = formatHeader(items.length, prefix ? { key: prefix } : undefined)
writer.push(depth, header) writer.push(depth, header)
for (const item of items) { for (const item of items) {
@@ -309,21 +278,6 @@ export function encodeMixedArrayAsListItems(
} }
} }
export function encodeArrayOfObjectsAsListItems(
prefix: string | undefined,
rows: readonly JsonObject[],
writer: LineWriter,
depth: Depth,
options: ResolvedEncodeOptions,
): void {
const header = prefix ? formatKeyedArrayHeader(prefix, rows.length) : formatArrayHeader(rows.length)
writer.push(depth, `${header}`)
for (const obj of rows) {
encodeObjectAsListItem(obj, writer, depth + 1, options)
}
}
export function encodeObjectAsListItem(obj: JsonObject, writer: LineWriter, depth: Depth, options: ResolvedEncodeOptions): void { export function encodeObjectAsListItem(obj: JsonObject, writer: LineWriter, depth: Depth, options: ResolvedEncodeOptions): void {
const keys = Object.keys(obj) const keys = Object.keys(obj)
if (keys.length === 0) { if (keys.length === 0) {

View File

@@ -131,24 +131,35 @@ export function joinEncodedValues(values: readonly JsonPrimitive[], delimiter: s
// #region Header formatters // #region Header formatters
export function formatArrayHeader(length: number): string { /**
return `[${length}]:` * Header formatter for arrays and tables with optional key prefix and field names
} */
export function formatHeader(
length: number,
options?: {
key?: string
fields?: readonly string[]
},
): string {
const key = options?.key
const fields = options?.fields
export function formatTabularHeader(length: number, fields: readonly string[]): string { let header = ''
const quotedFields = fields.map(f => encodeKey(f))
return `[${length}]{${quotedFields.join(',')}}:`
}
export function formatKeyedArrayHeader(key: string, length: number): string { if (key) {
const encodedKey = encodeKey(key) header += encodeKey(key)
return `${encodedKey}[${length}]:` }
}
export function formatKeyedTableHeader(key: string, length: number, fields: readonly string[]): string { header += `[${length}]`
const encodedKey = encodeKey(key)
const quotedFields = fields.map(f => encodeKey(f)) if (fields) {
return `${encodedKey}[${length}]{${quotedFields.join(',')}}:` const quotedFields = fields.map(f => encodeKey(f))
header += `{${quotedFields.join(',')}}`
}
header += ':'
return header
} }
// #endregion // #endregion