test: add case for unquoted invalid numeric formats as strings

This commit is contained in:
Johann Schopplich
2025-10-29 13:05:42 +01:00
parent b034c4455e
commit ee31be3bdc
12 changed files with 292 additions and 364 deletions

View File

@@ -1,26 +1,7 @@
import type {
Depth,
JsonArray,
JsonObject,
JsonPrimitive,
JsonValue,
ResolvedEncodeOptions,
} from '../types'
import type { Depth, JsonArray, JsonObject, JsonPrimitive, JsonValue, ResolvedEncodeOptions } from '../types'
import { LIST_ITEM_MARKER } from '../constants'
import {
isArrayOfArrays,
isArrayOfObjects,
isArrayOfPrimitives,
isJsonArray,
isJsonObject,
isJsonPrimitive,
} from './normalize'
import {
encodeAndJoinPrimitives,
encodeKey,
encodePrimitive,
formatHeader,
} from './primitives'
import { isArrayOfArrays, isArrayOfObjects, isArrayOfPrimitives, isJsonArray, isJsonObject, isJsonPrimitive } from './normalize'
import { encodeAndJoinPrimitives, encodeKey, encodePrimitive, formatHeader } from './primitives'
import { LineWriter } from './writer'
// #region Encode normalized JsonValue

View File

@@ -1,9 +1,4 @@
import type {
JsonArray,
JsonObject,
JsonPrimitive,
JsonValue,
} from '../types'
import type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from '../types'
// #region Normalization (unknown → JsonValue)

View File

@@ -1,14 +1,7 @@
import type { JsonPrimitive } from '../types'
import {
BACKSLASH,
COMMA,
DEFAULT_DELIMITER,
DOUBLE_QUOTE,
FALSE_LITERAL,
LIST_ITEM_MARKER,
NULL_LITERAL,
TRUE_LITERAL,
} from '../constants'
import { COMMA, DEFAULT_DELIMITER, DOUBLE_QUOTE, NULL_LITERAL } from '../constants'
import { escapeString } from '../shared/string-utils'
import { isSafeUnquoted, isValidUnquotedKey } from '../shared/validation'
// #region Primitive encoding
@@ -36,74 +29,6 @@ export function encodeStringLiteral(value: string, delimiter: string = COMMA): s
return `${DOUBLE_QUOTE}${escapeString(value)}${DOUBLE_QUOTE}`
}
export function escapeString(value: string): string {
return value
.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`)
.replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`)
.replace(/\n/g, `${BACKSLASH}n`)
.replace(/\r/g, `${BACKSLASH}r`)
.replace(/\t/g, `${BACKSLASH}t`)
}
export function isSafeUnquoted(value: string, delimiter: string = COMMA): boolean {
if (!value) {
return false
}
if (isPaddedWithWhitespace(value)) {
return false
}
if (value === TRUE_LITERAL || value === FALSE_LITERAL || value === NULL_LITERAL) {
return false
}
if (isNumericLike(value)) {
return false
}
// Check for colon (always structural)
if (value.includes(':')) {
return false
}
// Check for quotes and backslash (always need escaping)
if (value.includes('"') || value.includes('\\')) {
return false
}
// Check for brackets and braces (always structural)
if (/[[\]{}]/.test(value)) {
return false
}
// Check for control characters (newline, carriage return, tab - always need quoting/escaping)
if (/[\n\r\t]/.test(value)) {
return false
}
// Check for the active delimiter
if (value.includes(delimiter)) {
return false
}
// Check for hyphen at start (list marker)
if (value.startsWith(LIST_ITEM_MARKER)) {
return false
}
return true
}
function isNumericLike(value: string): boolean {
// Match numbers like: 42, -3.14, 1e-6, 05, etc.
return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value) || /^0\d+$/.test(value)
}
function isPaddedWithWhitespace(value: string): boolean {
return value !== value.trim()
}
// #endregion
// #region Key encoding
@@ -116,10 +41,6 @@ export function encodeKey(key: string): string {
return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`
}
function isValidUnquotedKey(key: string): boolean {
return /^[A-Z_][\w.]*$/i.test(key)
}
// #endregion
// #region Value joining
@@ -132,9 +53,6 @@ export function encodeAndJoinPrimitives(values: readonly JsonPrimitive[], delimi
// #region Header formatters
/**
* Header formatter for arrays and tables with optional key prefix and field names
*/
export function formatHeader(
length: number,
options?: {