Skip to content

Commit

Permalink
feat: implement immutableWarningDisabled and document immutability
Browse files Browse the repository at this point in the history
  • Loading branch information
josdejong committed Feb 28, 2024
1 parent e93ab2c commit 30fd460
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 2 deletions.
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,28 @@ mode: 'tree' | 'text' | 'table'

Open the editor in `'tree'` mode (default), `'table'` mode, or `'text'` mode (formerly: `code` mode).

#### immutable

```ts
immutable: boolean
```

The option `immutable` is `false` by default. It is higly recommended to configure the editor with `immmutable: true` and only make changes to the editor's contents in an immutable way. This gives _much_ better performane, and is a necessity when working with large JSON documents.

How to use the library in an immutable way, and why, is explained in detail in the section [Immutability](#immutability).

#### immutableWarningDisabled

```ts
immutableWarningDisabled: boolean
```

The option `immutableWarningDisabled` is `false` by default. When `svelte-jsoneditor` is configured with `{immutable: false}`, it will log the following console warning:

> JSONEditor is configured with {immutable:false}, which is bad for performance. Consider configuring {immutable:true}, or disable this warning by configuring {immutableWarningDisabled:true}.
If you really need support for mutable changes, you can suppress this warning by configuring `{immutableWarningDisabled: true}`.

#### mainMenuBar

```ts
Expand Down Expand Up @@ -937,6 +959,75 @@ When updating CSS variables dynamically, it is necessary to refresh the via `edi
<JSONEditor bind:this="{editorRef}" ... />
```
## Immutability
> TL;DR configure `svelte-jsoneditor` with `{immutable: true}` and only make immutable changes to you document contents for best performance. If you _do_ need support for mutable changes, you can disable the console warning by configuring `{immutableWarningDisabled: true}`.
The editor can support both _immutable_ and _mutable_ changes made to the contents of the editor. This can be configured with the option [`immutable`](#immutable). It is strongly recommended to configure the editor with `{immmutable: true}` and only make changes to the editor's contents in an _immutable_ way. This gives much better performance, and is a necessity when working with large JSON documents.
If you're making mutable changes, the editor has to make a full copy of the JSON document on every change to enable history (undo/redo) and to provide the `onChange` callback with both a current and previous version of the document. The editor also has to do a full rerender of the UI on every change, since it cannot know which part of the document has been changed.
When using immutable changes on the other hand, the editor knows exactly which part of the document is changed using a cheap strict equal check against the previous version of the data. There is no need to make a deep copy of the document on changes, and also, the change detection can be used to only rerender the parts of the UI that actually changed.
Here is an example of a mutable change:
```js
// mutable change (NOT RECOMMENDED)
function updateDate() {
const lastEdited = new Date().toISOString()
const content = toJsonContent(myJsonEditor.get())
content.json.lastEdited = lastEdited // <- this is a mutable change
myJsonEditor.update(content)
}
```
Instead, you can apply the same change in an immutable way. There are various options for that:
```js
// immutable change (RECOMMENDED)

// immutable change using a libary like "mutative" or "immer" (efficient and easy to work with)
import { create } from 'mutative'
function updateDate1() {
const content = toJsonContent(myJsonEditor.get())
const updatedContent = create(content, (draft) => {
draft.json.lastEdited = new Date().toISOString()
})
myJsonEditor.update(updatedContent)
}

// immutable change using "immutable-json-patch"
import { setIn } from 'immutable-json-patch'
function updateDate2() {
const content = toJsonContent(myJsonEditor.get())
const updatedContent = setIn(content, ['json', 'lastEdited'], new Date().toISOString())
myJsonEditor.update(updatedContent)
}

// immutable change using the spread operator (not handy for updates in nested data)
function updateDate3() {
const content = toJsonContent(myJsonEditor.get())
const updatedContent = {
json: {
...content.json,
lastEdited: new Date().toISOString()
}
}
myJsonEditor.update(updatedContent)
}

// immutable change by creating a deep clone (simple but inefficient)
import { cloneDeep } from 'lodash-es'
function updateDate4() {
const content = toJsonContent(myJsonEditor.get())
const updatedContent = cloneDeep(content)
updatedContent.json.lastEdited = new Date().toISOString()
myJsonEditor.update(updatedContent)
}
```
Besides performance benefits, another advantage of an immutable way of working is that it makes the data that you work with much more predictive and less error-prone. You can learn more about immutability by searching for articles or videos about the subject, such as [this video](https://youtu.be/Wo0qiGPSV-s) or [this article](https://www.freecodecamp.org/news/immutability-in-javascript-with-examples/). Immutability is not always the best choice, but in the case of this JSON Editor we're dealing with large and deeply nested data structures, in which we typically make only small changes like updating a single nested value. An immutable approach really shines here, enabling `svelte-jsoneditor` to smoothly render and edit JSON documents up to 512 MB.
## Differences between `josdejong/svelte-jsoneditor` and `josdejong/jsoneditor`
This library [`josdejong/svelte-jsoneditor`](https://github.com/josdejong/svelte-jsoneditor/) is the successor of [`josdejong/jsoneditor`](https://github.com/josdejong/jsoneditor). The main differences are:
Expand Down
16 changes: 14 additions & 2 deletions src/lib/components/JSONEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
export let selection: JSONEditorSelection | null = null
export let immutable = false
export let immutableWarningDisabled = false
export let readOnly = false
export let indentation: number | string = 2
export let tabSize = 4
Expand Down Expand Up @@ -113,6 +114,17 @@
callbacks: Partial<Callbacks>
} | null = null
$: {
if (!immutable && !immutableWarningDisabled) {
console.warn(
'JSONEditor is configured with {immutable:false}, which is bad for performance. ' +
'Consider configuring {immutable:true}, ' +
'or disable this warning by configuring {immutableWarningDisabled:true}. ' +
'Read more: https://github.com/josdejong/svelte-jsoneditor/?tab=readme-ov-file#immutability'
)
}
}
$: {
const contentError = validateContentType(content)
if (contentError) {
Expand Down Expand Up @@ -161,7 +173,7 @@
// new editor id -> will re-create the editor
instanceId = uniqueId()
content = immutable ? newContent : {...newContent}
content = immutable ? newContent : { ...newContent }
}
export async function update(updatedContent: Content): Promise<void> {
Expand All @@ -172,7 +184,7 @@
throw new Error(contentError)
}
content = immutable ? updatedContent : {...updatedContent}
content = immutable ? updatedContent : { ...updatedContent }
await tick() // await rerender
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ export interface AbsolutePopupContext {
export interface JSONEditorPropsOptional {
content?: Content
immutable?: boolean
immutableWarningDisabled?: boolean
readOnly?: boolean
indentation?: number | string
tabSize?: number
Expand Down

0 comments on commit 30fd460

Please sign in to comment.