Skip to content

Commit

Permalink
feat: implemented options parser and validationParser to support …
Browse files Browse the repository at this point in the history
…alternative JSON parsers like lossless-json (#151)

* feat: custom parser largely working (WIP)

* fix: extend `isTimestamp` to support `LosslessNumber` and `bigint`

* fix: show error when sorting throws an error

* fix: use the custom parser when validating content in `text` mode

* docs: describe the new property `parser` in the readme

* fix: truncate raw contents in development page

* fix: enforce string not working with custom parser

* chore: create a real `getAbsolutePath` function for use in the build scripts

* fix: mark the package as side-effects free, allowing better optimization in bundlers

* chore: fix merge conflicts

* fix: upgrade to `immutable-json-patch@5.0.0`, fixing search

* chore: cleanup logging

* fix: support `bigint` in the query languages

* feat: rerender the editor when parser changes

* feat: implement `validationParser`

* fix: only validate when a validator function is provided

* fix: do not show parse or validation errors when the editor is disabled

* chore: make all properties of the internal TextMode and TreeMode required, remove the default values

* docs: add homepage in package.json

* chore: reorder fields in package.json

* fix: export util function `isEqualParser`

* chore: update all dependencies, including `lossless-json@2.0.0`
  • Loading branch information
josdejong authored Sep 29, 2022
1 parent af5d12f commit b47368b
Show file tree
Hide file tree
Showing 59 changed files with 1,832 additions and 1,769 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
env: {
browser: true,
es2017: true,
es2020: true,
node: true,
mocha: true
}
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ const editor = new JSONEditor({
const validator = createAjvValidator(schema, schemaDefinitions)
```

- `parser: JSON = JSON`. Configure a custom JSON parser, like [`lossless-json`](https://github.com/josdejong/lossless-json). By default, the native `JSON` parser of JavaScript is used. The `JSON` interface is an object with a `parse` and `stringify` function.

- `validationParser: JSON = JSON`. Only applicable when a `validator` is provided. This is the same as `parser`, except that this parser is used to parse the data before sending it to the validator. Configure a custom JSON parser that is used to parse JSON before passing it to the `validator`. By default, the built-in `JSON` parser is used. When passing a custom `validationParser`, make sure the output of the parser is supported by the configured `validator`. So, when the `validationParser` can output `bigint` numbers or other numeric types, the `validator` must also support that. In tree mode, when `parser` is not equal to `validationParser`, the JSON document will be converted before it is passed to the `validator` via `validationParser.parse(parser.stringify(json))`.

- `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 of the contents, both changes made by a user and programmatic changes made via methods like `.set()`, `.update()`, or `.patch()`. The parameter `patchResult` is only available in `tree` mode, and not in `text` mode, since a change in arbitrary text cannot be expressed as a JSON Patch document.
Expand Down
1,948 changes: 716 additions & 1,232 deletions package-lock.json

Large diffs are not rendered by default.

57 changes: 30 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
"name": "svelte-jsoneditor",
"description": "A web-based tool to view, edit, format, transform, and validate JSON",
"version": "0.7.6",
"homepage": "https://jsoneditoronline.org",
"repository": {
"type": "git",
"url": "https://github.com/josdejong/svelte-jsoneditor.git"
},
"type": "module",
"svelte": "index.js",
"module": "index.js",
"main": "index.js",
"types": "index.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/josdejong/svelte-jsoneditor.git"
},
"license": "ISC",
"scripts": {
"dev": "vite dev",
Expand All @@ -31,6 +32,7 @@
"build:vanilla:copy:readme": "cpy --rename README.md README-VANILLA.md package-vanilla",
"build:vanilla:copy:themes": "cpy --flat src/lib/themes package-vanilla/themes",
"build:vanilla:package-json": "node tools/createVanillaPackageJson.js",
"build-and-test": "npm test && npm run lint && npm run build",
"test": "mocha",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
Expand All @@ -50,73 +52,74 @@
"dry-run": "standard-version --dry-run"
},
"dependencies": {
"@codemirror/commands": "^6.1.0",
"@codemirror/commands": "^6.1.1",
"@codemirror/lang-json": "^6.0.0",
"@codemirror/language": "^6.2.1",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.2.0",
"@codemirror/state": "^6.1.1",
"@codemirror/view": "^6.2.3",
"@codemirror/search": "^6.2.1",
"@codemirror/state": "^6.1.2",
"@codemirror/view": "^6.3.0",
"@fontsource/fira-mono": "^4.5.9",
"@fortawesome/free-regular-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"ajv-dist": "^8.11.0",
"classnames": "^2.3.1",
"classnames": "^2.3.2",
"codemirror": "^6.0.1",
"diff-sequences": "^29.0.0",
"immutable-json-patch": "^4.0.1",
"immutable-json-patch": "^5.1.0",
"jmespath": "^0.16.0",
"json-source-map": "^0.6.1",
"jsonrepair": "^2.2.1",
"lodash-es": "^4.17.21",
"memoize-one": "^6.0.0",
"natural-compare-lite": "^1.4.0",
"sass": "^1.54.9",
"sass": "^1.55.0",
"svelte": "^3.50.1",
"svelte-awesome": "^3.0.0",
"svelte-select": "^4.4.7",
"svelte-simple-modal": "^1.4.1",
"vanilla-picker": "^2.12.1"
},
"devDependencies": {
"@babel/core": "7.19.0",
"@babel/preset-env": "7.19.0",
"@babel/core": "7.19.3",
"@babel/preset-env": "7.19.3",
"@commitlint/cli": "17.1.2",
"@commitlint/config-conventional": "17.1.0",
"@rollup/plugin-babel": "5.3.1",
"@rollup/plugin-commonjs": "22.0.2",
"@rollup/plugin-json": "4.1.0",
"@rollup/plugin-node-resolve": "14.0.1",
"@rollup/plugin-node-resolve": "14.1.0",
"@rollup/plugin-typescript": "8.5.0",
"@sveltejs/adapter-auto": "1.0.0-next.72",
"@sveltejs/kit": "1.0.0-next.480",
"@sveltejs/package": "1.0.0-next.3",
"@sveltejs/adapter-auto": "1.0.0-next.80",
"@sveltejs/kit": "1.0.0-next.504",
"@sveltejs/package": "1.0.0-next.5",
"@types/cookie": "0.5.1",
"@types/lodash-es": "4.17.6",
"@types/mocha": "9.1.1",
"@typescript-eslint/eslint-plugin": "5.36.2",
"@typescript-eslint/parser": "5.36.2",
"@types/mocha": "10.0.0",
"@typescript-eslint/eslint-plugin": "5.38.1",
"@typescript-eslint/parser": "5.38.1",
"cpy-cli": "4.2.0",
"del-cli": "5.0.0",
"eslint": "8.22.0",
"eslint": "8.24.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-svelte3": "4.0.0",
"husky": "8.0.1",
"lossless-json": "2.0.0",
"mocha": "10.0.0",
"npm-run-all": "4.1.5",
"prettier": "2.7.1",
"prettier-plugin-svelte": "2.7.0",
"rollup": "2.79.0",
"prettier-plugin-svelte": "2.7.1",
"rollup": "2.79.1",
"rollup-plugin-dts": "4.2.2",
"rollup-plugin-svelte": "7.1.0",
"rollup-plugin-terser": "7.0.2",
"standard-version": "9.5.0",
"svelte-check": "2.9.0",
"svelte-check": "2.9.1",
"svelte-preprocess": "4.10.7",
"svelte2tsx": "0.5.16",
"svelte2tsx": "0.5.19",
"ts-node": "10.9.1",
"tslib": "2.4.0",
"typescript": "4.8.3",
"vite": "3.1.0"
"typescript": "4.8.4",
"vite": "3.1.4"
}
}
25 changes: 23 additions & 2 deletions src/lib/components/JSONEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Modal from 'svelte-simple-modal'
import { SORT_MODAL_OPTIONS, TRANSFORM_MODAL_OPTIONS } from '../constants.js'
import { uniqueId } from '../utils/uniqueId.js'
import { isTextContent, validateContentType } from '../utils/jsonUtils'
import { isEqualParser, isTextContent, validateContentType } from '../utils/jsonUtils'
import AbsolutePopup from './modals/popup/AbsolutePopup.svelte'
import TextMode from './modes/textmode/TextMode.svelte'
import TreeMode from './modes/treemode/TreeMode.svelte'
Expand All @@ -19,6 +19,7 @@
Content,
ContentErrors,
JSONEditorPropsOptional,
JSONParser,
JSONPatchResult,
MenuItem,
MenuSeparatorItem,
Expand Down Expand Up @@ -57,7 +58,9 @@
export let statusBar = true
export let escapeControlCharacters = false
export let escapeUnicodeCharacters = false
export let parser: JSONParser = JSON
export let validator: Validator | null = null
export let validationParser: JSONParser = JSON
export let queryLanguages: QueryLanguage[] = [javascriptQueryLanguage]
export let queryLanguageId: string = queryLanguages[0].id
Expand Down Expand Up @@ -92,6 +95,19 @@
}
}
// rerender the full editor when the parser changes. This is needed because
// numeric state is hold at many places in the editor.
let previousParser = parser
$: {
if (!isEqualParser(parser, previousParser)) {
debug('parser changed, recreate editor')
previousParser = parser
// new editor id -> will re-create the editor
instanceId = uniqueId()
}
}
export function get(): Content {
return content
}
Expand Down Expand Up @@ -125,7 +141,7 @@
if (isTextContent(content)) {
try {
content = {
json: JSON.parse(content.text),
json: parser.parse(content.text),
text: undefined
}
} catch (err) {
Expand Down Expand Up @@ -348,6 +364,7 @@
selectedPath,
escapeControlCharacters,
escapeUnicodeCharacters,
parser,
queryLanguages,
queryLanguageId,
onChangeQueryLanguage: handleChangeQueryLanguage,
Expand Down Expand Up @@ -410,7 +427,9 @@
{mainMenuBar}
{statusBar}
{escapeUnicodeCharacters}
{parser}
{validator}
{validationParser}
onChange={handleChange}
onSwitchToTreeMode={handleSwitchToTreeMode}
{onError}
Expand All @@ -431,7 +450,9 @@
{navigationBar}
{escapeControlCharacters}
{escapeUnicodeCharacters}
{parser}
{validator}
{validationParser}
{onError}
onChange={handleChange}
onRequestRepair={handleRequestRepair}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<svelte:options immutable={true} />

<script lang="ts">
import type { JSONData, JSONPath } from 'immutable-json-patch'
import type { JSONValue, JSONPath } from 'immutable-json-patch'
import { getIn } from 'immutable-json-patch'
import { range } from 'lodash-es'
import { isObject, isObjectOrArray } from '../../../utils/typeUtils'
Expand All @@ -13,7 +13,7 @@
const debug = createDebug('jsoneditor:NavigationBar')
export let json: JSONData
export let json: JSONValue
export let documentState: DocumentState
export let onSelect: OnSelect
Expand Down
4 changes: 4 additions & 0 deletions src/lib/components/modals/SortModal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,9 @@

.jse-space {
height: 200px; // Trick for the property select box dropdown to be fully visible

.jse-error {
color: var(--jse-error-color);
}
}
}
53 changes: 33 additions & 20 deletions src/lib/components/modals/SortModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
import { stringifyPath } from '../../utils/pathUtils.js'
import { sortArray, sortObjectKeys } from '../../logic/sort.js'
import { sortModalState } from './sortModalState.js'
import type { JSONData, JSONPath } from 'immutable-json-patch'
import type { JSONValue, JSONPath } from 'immutable-json-patch'
import { compileJSONPointer, getIn } from 'immutable-json-patch'
import { createDebug } from '../../utils/debug'
import type { OnSort } from '../../types'
const debug = createDebug('jsoneditor:SortModal')
export let id: string
export let json: JSONData // the whole document
export let json: JSONValue // the whole document
export let selectedPath: JSONPath
export let onSort: OnSort
Expand All @@ -44,6 +44,7 @@
(sortModalState[stateId] && sortModalState[stateId].selectedProperty) || undefined
let selectedDirection =
(sortModalState[stateId] && sortModalState[stateId].selectedDirection) || asc
let sortError: string | undefined = undefined
$: {
// if there is only one option, select it and do not render the select box
Expand Down Expand Up @@ -71,26 +72,32 @@
}
function handleSort() {
if (jsonIsArray) {
if (!selectedProperty) {
return
try {
sortError = undefined
if (jsonIsArray) {
if (!selectedProperty) {
return
}
const property = selectedProperty.value
const direction = selectedDirection.value
const operations = sortArray(json, selectedPath, property, direction)
onSort(operations)
} else if (isObject(selectedJson)) {
const direction = selectedDirection.value
const operations = sortObjectKeys(json, selectedPath, direction)
onSort(operations)
} else {
console.error('Cannot sort: no array or object')
}
const property = selectedProperty.value
const direction = selectedDirection.value
const operations = sortArray(json, selectedPath, property, direction)
onSort(operations)
} else if (isObject(selectedJson)) {
const direction = selectedDirection.value
const operations = sortObjectKeys(json, selectedPath, direction)
onSort(operations)
} else {
console.error('Cannot sort: no array or object')
close()
} catch (err) {
sortError = err.toString()
}
close()
}
function focus(element: HTMLElement) {
Expand Down Expand Up @@ -142,7 +149,13 @@
</tbody>
</table>

<div class="jse-space" />
<div class="jse-space">
{#if sortError}
<div class="jse-error">
{sortError}
</div>
{/if}
</div>

<div class="jse-actions">
<button
Expand Down
Loading

0 comments on commit b47368b

Please sign in to comment.