Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/toon/src/encode/encoders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ export function* encodeArrayOfArraysAsListItemsLines(
}
}

export function encodeInlineArrayLine(values: readonly JsonPrimitive[], delimiter: string, prefix?: string): string {
export function encodeInlineArrayLine(values: readonly JsonPrimitive[], delimiter: string, prefix?: string, quoteStrings?: boolean): string {
const header = formatHeader(values.length, { key: prefix, delimiter })
const joinedValue = encodeAndJoinPrimitives(values, delimiter)
const joinedValue = encodeAndJoinPrimitives(values, delimiter, quoteStrings)

if (values.length === 0)
return header
Expand Down
16 changes: 10 additions & 6 deletions packages/toon/src/encode/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { isSafeUnquoted, isValidUnquotedKey } from '../shared/validation'

// #region Primitive encoding

export function encodePrimitive(value: JsonPrimitive, delimiter?: string): string {

export function encodePrimitive(value: JsonPrimitive, delimiter?: string, quoteStrings?: boolean): string {
if (value === null) {
return NULL_LITERAL
}
Expand All @@ -18,14 +19,17 @@ export function encodePrimitive(value: JsonPrimitive, delimiter?: string): strin
return String(value)
}

return encodeStringLiteral(value, delimiter)
return encodeStringLiteral(value, delimiter, quoteStrings)
}

export function encodeStringLiteral(value: string, delimiter: string = DEFAULT_DELIMITER): string {

export function encodeStringLiteral(value: string, delimiter: string = DEFAULT_DELIMITER, quoteStrings?: boolean): string {
if (quoteStrings) {
return `${DOUBLE_QUOTE}${escapeString(value)}${DOUBLE_QUOTE}`
}
if (isSafeUnquoted(value, delimiter)) {
return value
}

return `${DOUBLE_QUOTE}${escapeString(value)}${DOUBLE_QUOTE}`
}

Expand All @@ -45,8 +49,8 @@ export function encodeKey(key: string): string {

// #region Value joining

export function encodeAndJoinPrimitives(values: readonly JsonPrimitive[], delimiter: string = DEFAULT_DELIMITER): string {
return values.map(v => encodePrimitive(v, delimiter)).join(delimiter)
export function encodeAndJoinPrimitives(values: readonly JsonPrimitive[], delimiter: string = DEFAULT_DELIMITER, quoteStrings?: boolean): string {
return values.map(v => encodePrimitive(v, delimiter, quoteStrings)).join(delimiter)
}

// #endregion
Expand Down
1 change: 1 addition & 0 deletions packages/toon/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ function resolveOptions(options?: EncodeOptions): ResolvedEncodeOptions {
delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
keyFolding: options?.keyFolding ?? 'off',
flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY,
quoteStrings: options?.quoteStrings ?? false,
replacer: options?.replacer,
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/toon/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export interface EncodeOptions {
* @default Infinity
*/
flattenDepth?: number
/**
* Always wrap string values in quotes during encoding.
* Useful for consistent string serialization or when building formats that require strict quoting.
* @default false
*/
quoteStrings?: boolean
/**
* A function to transform or filter values during encoding.
* Called for the root value and every nested property/element.
Expand Down
26 changes: 26 additions & 0 deletions packages/toon/test/encode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,37 @@ for (const fixtures of fixtureFiles) {
})
}


function resolveEncodeOptions(options?: TestCase['options']): ResolvedEncodeOptions {
return {
indent: options?.indent ?? 2,
delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
keyFolding: options?.keyFolding ?? 'off',
flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY,
quoteStrings: options?.quoteStrings ?? false,
}
}

describe('encode (quoteStrings option)', () => {
it('should quote all string values when quoteStrings is true', () => {
const input = { a: 'foo', b: 'bar baz', c: 42, d: true }
const expected = 'a: "foo"\nb: "bar baz"\nc: 42\nd: true'
const result = encode(input, { quoteStrings: true })
expect(result).toBe(expected)
})

it('should quote strings in arrays when quoteStrings is true', () => {
const input = { arr: ['x', 'y z', 'w'] }
const expected = 'arr[3]: "x","y z","w"'
const result = encode(input, { quoteStrings: true })
expect(result).toBe(expected)
})

it('should not quote strings when quoteStrings is false (default)', () => {
const input = { a: 'foo', b: 'bar baz', c: 42 }
// Only b should be quoted because it contains a space
const expected = 'a: foo\nb: "bar baz"\nc: 42'
const result = encode(input)
expect(result).toBe(expected)
})
})