Files
TREK/server/tests/unit/nest/llm-parse/lenient-json.test.ts
T
fbnlrz 9ad3507e57 fix(llm-parse): tolerate non-strict JSON from Gemini and similar models
Gemini reached through the OpenAI-compatible endpoint returns
JavaScript-object-literal output — single-quoted strings, unquoted keys,
trailing commas — which strict JSON.parse rejects. The reservation list
then came back empty and the booking-import UI showed nothing (#1638).

Add a shared parseLenientJson() that tries strict JSON first and falls
back to JSON5 (which accepts exactly that relaxed superset), and route
both the OpenAI-compatible and Ollama-native parse paths through it,
replacing the two identical strict parsers.
2026-07-24 12:42:38 +02:00

50 lines
1.5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { parseLenientJson } from '../../../../src/nest/llm-parse/lenient-json';
describe('parseLenientJson', () => {
it('parses strict JSON', () => {
expect(parseLenientJson('{"a":1,"b":"x"}')).toEqual({ a: 1, b: 'x' });
});
it('strips a ```json code fence', () => {
expect(parseLenientJson('```json\n[{"@type":"FlightReservation"}]\n```')).toEqual([
{ '@type': 'FlightReservation' },
]);
});
it('parses single-quoted, unquoted-key, trailing-comma output (Gemini, #1638)', () => {
const gemini = `[
{
'@type': 'LodgingReservation',
checkinTime: '2026-08-28T00:00:00',
checkoutTime: '2026-08-30T11:00:00',
price: 146.25,
priceCurrency: 'EUR',
}
]`;
expect(parseLenientJson(gemini)).toEqual([
{
'@type': 'LodgingReservation',
checkinTime: '2026-08-28T00:00:00',
checkoutTime: '2026-08-30T11:00:00',
price: 146.25,
priceCurrency: 'EUR',
},
]);
});
it('parses a code-fenced non-strict object', () => {
expect(parseLenientJson("```\n{ reservations: [{ '@type': 'TrainReservation' }] }\n```")).toEqual({
reservations: [{ '@type': 'TrainReservation' }],
});
});
it('returns null on empty or truly unparseable input', () => {
expect(parseLenientJson('')).toBeNull();
expect(parseLenientJson(null)).toBeNull();
expect(parseLenientJson(undefined)).toBeNull();
expect(parseLenientJson('this is prose, not json')).toBeNull();
});
});