diff --git a/README.md b/README.md index 8357aaf6..610884a9 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ const editor = new JSONEditor({ - `onError(err: Error)`. Callback fired when an error occurs. Default implementation is to log an error in the console and show a simple alert message to the user. -- `onChange(content: Content, previousContent: Content, changeStatus: { contentErrors: ContentErrors, patchResult: JSONPatchResult | null })`. The callback which is invoked on every change of the contents, both changes made by a user and programmatic changes made via methods like `.set()`, `.update()`, or `.patch()`. +- `onChange(content: Content, previousContent: Content, changeStatus: { contentErrors: ContentErrors | null, patchResult: JSONPatchResult | null })`. The callback which is invoked on every change of the contents, both changes made by a user and programmatic changes made via methods like `.set()`, `.update()`, or `.patch()`. The returned `content` is sometimes of type `{ json }`, and sometimes of type `{ text }`. Which of the two is returned depends on the mode of the editor, the change that is applied, and the state of the document (valid, invalid, empty). Please be aware that `{ text }` can contain invalid JSON: whilst typing in `text` mode, a JSON document will be temporarily invalid, like when the user is typing a new string. The parameter `patchResult` is only returned on changes that can be represented as a JSON Patch document, and for example not when freely typing in `text` mode. - `onChangeMode(mode: 'tree' | 'text' | 'table')`. Invoked when the mode is changed. - `onClassName(path: Path, value: any): string | undefined`. @@ -351,7 +351,7 @@ const editor = new JSONEditor({ - `findElement(path: Path)` Find the DOM element of a given path. Returns `null` when not found. - `acceptAutoRepair(): Content` In tree mode, invalid JSON is automatically repaired when loaded. When the repair was successful, the repaired contents are rendered but not yet applied to the document itself until the user clicks "Ok" or starts editing the data. Instead of accepting the repair, the user can also click "Repair manually instead". Invoking `.acceptAutoRepair()` will programmatically accept the repair. This will trigger an update, and the method itself also returns the updated contents. In case of `text` mode or when the editor is not in an "accept auto repair" status, nothing will happen, and the contents will be returned as is. - `refresh()`. Refresh rendering of the contents, for example after changing the font size. This is only available in `text` mode. -- `validate() : ContentErrors`. Get all current parse errors and validation errors. +- `validate() : ContentErrors | null`. Get all current parse errors and validation errors. - `focus()`. Give the editor focus. - `destroy()`. Destroy the editor, remove it from the DOM. diff --git a/src/lib/components/JSONEditor.svelte b/src/lib/components/JSONEditor.svelte index 558e9bd0..77862147 100644 --- a/src/lib/components/JSONEditor.svelte +++ b/src/lib/components/JSONEditor.svelte @@ -197,7 +197,7 @@ * Validate the contents of the editor using the configured validator. * Returns a parse error or a list with validation warnings */ - export function validate(): ContentErrors { + export function validate(): ContentErrors | null { return refJSONEditorRoot.validate() } diff --git a/src/lib/components/modes/JSONEditorRoot.svelte b/src/lib/components/modes/JSONEditorRoot.svelte index 7ce06c63..3b8113bc 100644 --- a/src/lib/components/modes/JSONEditorRoot.svelte +++ b/src/lib/components/modes/JSONEditorRoot.svelte @@ -151,7 +151,7 @@ * Validate the contents of the editor using the configured validator. * Returns a parse error or a list with validation warnings */ - export function validate(): ContentErrors { + export function validate(): ContentErrors | null { if (refTextMode) { return refTextMode.validate() } else if (refTreeMode) { diff --git a/src/lib/components/modes/tablemode/TableMode.svelte b/src/lib/components/modes/tablemode/TableMode.svelte index 12367d1b..5f029914 100644 --- a/src/lib/components/modes/tablemode/TableMode.svelte +++ b/src/lib/components/modes/tablemode/TableMode.svelte @@ -527,7 +527,7 @@ ) } - export function validate(): ContentErrors { + export function validate(): ContentErrors | null { debug('validate') if (parseError) { @@ -540,9 +540,7 @@ // make sure the validation results are up-to-date // normally, they are only updated on the next tick after the json is changed updateValidationErrors(json, validator, parser, validationParser) - return { - validationErrors - } + return !isEmpty(validationErrors) ? { validationErrors } : null } export function patch( diff --git a/src/lib/components/modes/textmode/TextMode.svelte b/src/lib/components/modes/textmode/TextMode.svelte index 476d500a..d6c7442f 100644 --- a/src/lib/components/modes/textmode/TextMode.svelte +++ b/src/lib/components/modes/textmode/TextMode.svelte @@ -780,7 +780,7 @@ return [] } - export function validate(): ContentErrors { + export function validate(): ContentErrors | null { debug('validate:start') onChangeCodeMirrorValueDebounced.flush() @@ -799,7 +799,7 @@ } else { jsonStatus = JSON_STATUS_VALID jsonParseError = null - validationErrors = contentErrors.validationErrors + validationErrors = contentErrors?.validationErrors || [] } debug('validate:end') diff --git a/src/lib/components/modes/treemode/TreeMode.svelte b/src/lib/components/modes/treemode/TreeMode.svelte index 12e0f79e..e4a6913f 100644 --- a/src/lib/components/modes/treemode/TreeMode.svelte +++ b/src/lib/components/modes/treemode/TreeMode.svelte @@ -436,7 +436,7 @@ ) } - export function validate(): ContentErrors { + export function validate(): ContentErrors | null { debug('validate') if (parseError) { @@ -449,9 +449,7 @@ // make sure the validation results are up-to-date // normally, they are only updated on the next tick after the json is changed updateValidationErrors(json, validator, parser, validationParser) - return { - validationErrors - } + return !isEmpty(validationErrors) ? { validationErrors } : null } export function getJson() { diff --git a/src/lib/logic/actions.ts b/src/lib/logic/actions.ts index e805ab07..edade796 100644 --- a/src/lib/logic/actions.ts +++ b/src/lib/logic/actions.ts @@ -243,7 +243,7 @@ export function onRemove({ { text: '', json: undefined }, json !== undefined ? { text, json } : { text: text || '', json }, { - contentErrors: { validationErrors: [] }, + contentErrors: null, patchResult: null } ) diff --git a/src/lib/logic/validation.test.ts b/src/lib/logic/validation.test.ts index 551dbecd..3acb4cb3 100644 --- a/src/lib/logic/validation.test.ts +++ b/src/lib/logic/validation.test.ts @@ -164,9 +164,7 @@ describe('validation', () => { const invalidText = '{ "foo": 42 }' test('should validateText with native parser and valid JSON', () => { - deepStrictEqual(validateText(validText, myValidator, JSON, JSON), { - validationErrors: [] - }) + deepStrictEqual(validateText(validText, myValidator, JSON, JSON), null) }) test('should validateText with native parser and invalid JSON', () => { @@ -176,9 +174,7 @@ describe('validation', () => { }) test('should validateText with lossless parser and valid JSON', () => { - deepStrictEqual(validateText(validText, myValidator, LosslessJSONParser, JSON), { - validationErrors: [] - }) + deepStrictEqual(validateText(validText, myValidator, LosslessJSONParser, JSON), null) }) test('should validateText with lossless parser and invalid JSON', () => { @@ -190,9 +186,7 @@ describe('validation', () => { test('should validateText with two lossless parsers and valid JSON', () => { deepStrictEqual( validateText(validText, myLosslessValidator, LosslessJSONParser, LosslessJSONParser), - { - validationErrors: [] - } + null ) }) diff --git a/src/lib/logic/validation.ts b/src/lib/logic/validation.ts index bd297edd..2f675ebe 100644 --- a/src/lib/logic/validation.ts +++ b/src/lib/logic/validation.ts @@ -1,4 +1,4 @@ -import { initial } from 'lodash-es' +import { initial, isEmpty } from 'lodash-es' import type { ContentErrors, JSONParser, @@ -82,7 +82,7 @@ export function validateText( validator: Validator | null, parser: JSONParser, validationParser: JSONParser -): ContentErrors { +): ContentErrors | null { debug('validateText') if (text.length > MAX_VALIDATABLE_SIZE) { @@ -99,9 +99,7 @@ export function validateText( if (text.length === 0) { // new, empty document, do not try to parse - return { - validationErrors: [] - } + return null } try { @@ -113,9 +111,7 @@ export function validateText( ) if (!validator) { - return { - validationErrors: [] - } + return null } // if needed, parse with the validationParser to be able to feed the json to the validator @@ -133,7 +129,7 @@ export function validateText( (duration) => debug(`validate: validated json in ${duration} ms`) ) - return { validationErrors } + return !isEmpty(validationErrors) ? { validationErrors } : null } catch (err) { const isRepairable = measure( () => canAutoRepair(text, parser), diff --git a/src/lib/typeguards.ts b/src/lib/typeguards.ts index a3caa54e..3c16b94c 100644 --- a/src/lib/typeguards.ts +++ b/src/lib/typeguards.ts @@ -1,5 +1,4 @@ import type { - ContentErrors, ContentParseError, ContentValidationErrors, ContextMenuColumn, @@ -11,6 +10,7 @@ import type { MenuSpace, MenuSpaceItem } from './types.js' +import { isObject } from '$lib/utils/typeUtils' export function isMenuSpaceItem(item: unknown): item is MenuSpaceItem { return isMenuSpace(item) @@ -69,18 +69,12 @@ export function isContextMenuColumn(item: unknown): item is ContextMenuColumn { return item ? item['type'] === 'column' && Array.isArray(item['items']) : false } -export function isContentParseError( - contentErrors: ContentErrors -): contentErrors is ContentParseError { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return typeof contentErrors['parseError'] === 'object' && contentErrors['parseError'] !== null +export function isContentParseError(contentErrors: unknown): contentErrors is ContentParseError { + return isObject(contentErrors) && isObject(contentErrors['parseError']) } export function isContentValidationErrors( - contentErrors: ContentErrors + contentErrors: unknown ): contentErrors is ContentValidationErrors { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return Array.isArray(contentErrors['validationErrors']) + return isObject(contentErrors) && Array.isArray(contentErrors['validationErrors']) } diff --git a/src/lib/types.ts b/src/lib/types.ts index bf286147..dcbd464d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -285,7 +285,7 @@ export interface QueryLanguageOptions { export type OnChangeQueryLanguage = (queryLanguageId: string) => void export interface OnChangeStatus { - contentErrors: ContentErrors + contentErrors: ContentErrors | null patchResult: JSONPatchResult | null } export type OnChange =