You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-19 06:56:19 +00:00
feat(utils): honour __NITRO_JSON_MODE__ flag in JsonParser
Adds three explicit parsing strategies selectable at host build time via
the compile-time constant __NITRO_JSON_MODE__:
- legacy: strict JSON.parse only; clear error suggesting JSON5 mode
- json5 : JSON5.parse only
- auto : try JSON, fall back to JSON5 (existing behaviour and default
when the flag is undefined, so older hosts keep working)
URL/MIME hints for .json5 sources are still respected. README updated
with the modes table and a Vite wiring example.
This commit is contained in:
@@ -15,3 +15,50 @@ yarn
|
||||
```
|
||||
yarn add @nitrots/nitro-renderer
|
||||
```
|
||||
|
||||
## JSON / JSON5 configuration parser
|
||||
|
||||
Every configuration file and gamedata file loaded by the renderer (figuredata,
|
||||
furnidata, productdata, effectmap, avatar actions, etc.) goes through
|
||||
`@nitrots/utils` → `JsonParser.ts`. The parser supports three modes, selected at
|
||||
the **host build time** through the compile-time constant `__NITRO_JSON_MODE__`:
|
||||
|
||||
| Mode | Behaviour |
|
||||
|----------|---------------------------------------------------------------------------|
|
||||
| `legacy` | Strict `JSON.parse` only. Comments / trailing commas raise a clear error. |
|
||||
| `json5` | `JSON5.parse` only. Accepts comments, trailing commas, single quotes. |
|
||||
| `auto` | Try strict JSON first, fall back to JSON5. Default when the flag is unset.|
|
||||
|
||||
URL hints are still honoured: files ending in `.json5` (or served with a
|
||||
`application/json5` content-type) always go through JSON5, regardless of mode.
|
||||
|
||||
### Wiring the flag into a host
|
||||
|
||||
The renderer does **not** ship its own build for the flag — the host application
|
||||
(typically [Nitro V3](https://github.com/duckietm/Nitro-V3.git)) defines it via
|
||||
its bundler. Example with Vite:
|
||||
|
||||
```js
|
||||
// vite.config.mjs in the host
|
||||
export default defineConfig({
|
||||
define: {
|
||||
__NITRO_JSON_MODE__: JSON.stringify('json5') // or 'legacy' / 'auto'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If the constant is not defined the parser falls back to `auto`, which preserves
|
||||
the original behaviour of older releases — so existing hosts keep working
|
||||
without any change.
|
||||
|
||||
### Using the parser directly
|
||||
|
||||
```ts
|
||||
import { parseConfigJson, fetchConfigJson } from '@nitrots/utils';
|
||||
|
||||
const data = parseConfigJson<MyConfig>(rawText, '/configuration/ui-config.json');
|
||||
const data2 = await fetchConfigJson<MyConfig>('/configuration/ui-config.json5');
|
||||
```
|
||||
|
||||
Errors carry the source URL and, in `legacy` mode, a hint about switching to
|
||||
JSON5 — making misconfigurations easy to diagnose in production logs.
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
import JSON5 from 'json5';
|
||||
|
||||
declare const __NITRO_JSON_MODE__: 'legacy' | 'json5' | 'auto' | undefined;
|
||||
|
||||
const JSON5_EXTENSION = /\.json5(?:[?#]|$)/i;
|
||||
const JSON5_MIME = /(?:application|text)\/(?:json5|x-json5)/i;
|
||||
|
||||
const resolveJsonMode = (): 'legacy' | 'json5' | 'auto' =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if(typeof __NITRO_JSON_MODE__ !== 'undefined' && __NITRO_JSON_MODE__)
|
||||
{
|
||||
if(__NITRO_JSON_MODE__ === 'legacy' || __NITRO_JSON_MODE__ === 'json5' || __NITRO_JSON_MODE__ === 'auto') return __NITRO_JSON_MODE__;
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
|
||||
return 'auto';
|
||||
};
|
||||
|
||||
const looksLikeJson5Url = (url: string): boolean => !!url && JSON5_EXTENSION.test(url);
|
||||
|
||||
const looksLikeJson5ContentType = (contentType: string): boolean => !!contentType && JSON5_MIME.test(contentType);
|
||||
@@ -18,13 +34,34 @@ const formatParseError = (sourceUrl: string, strictError: unknown, json5Error: u
|
||||
return `Failed to parse JSON/JSON5${ source } — JSON5: ${ json5Message } (strict JSON: ${ strictMessage })`;
|
||||
};
|
||||
|
||||
const formatStrictError = (sourceUrl: string, err: unknown): string =>
|
||||
{
|
||||
const message = (err as Error)?.message || String(err);
|
||||
const source = sourceUrl ? ` in "${ sourceUrl }"` : '';
|
||||
|
||||
return `Failed to parse strict JSON${ source } — ${ message } (build is in 'legacy' mode; switch to JSON5 mode via 'yarn configure' to accept comments/trailing commas)`;
|
||||
};
|
||||
|
||||
export const parseConfigJson = <T = any>(text: string, sourceUrl: string = ''): T =>
|
||||
{
|
||||
if(text === null || text === undefined) throw new Error(`Empty response${ sourceUrl ? ` for "${ sourceUrl }"` : '' }`);
|
||||
|
||||
const trimmed = text.length > 0 ? text : '';
|
||||
const mode = resolveJsonMode();
|
||||
|
||||
if(looksLikeJson5Url(sourceUrl))
|
||||
if(mode === 'legacy')
|
||||
{
|
||||
try
|
||||
{
|
||||
return JSON.parse(trimmed) as T;
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
throw new Error(formatStrictError(sourceUrl, err));
|
||||
}
|
||||
}
|
||||
|
||||
if(mode === 'json5' || looksLikeJson5Url(sourceUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -62,8 +99,9 @@ export const parseConfigJsonFromResponse = async <T = any>(response: Response, s
|
||||
const contentType = response.headers?.get?.('content-type') || '';
|
||||
const text = await response.text();
|
||||
const url = sourceUrl || (response as any).url || '';
|
||||
const mode = resolveJsonMode();
|
||||
|
||||
if(looksLikeJson5ContentType(contentType) && !looksLikeJson5Url(url))
|
||||
if(mode === 'auto' && looksLikeJson5ContentType(contentType) && !looksLikeJson5Url(url))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user