Skip to content

Commit

Permalink
fix: refine the JSONParser type definition
Browse files Browse the repository at this point in the history
  • Loading branch information
josdejong committed Dec 14, 2023
1 parent 2da33de commit 217d6d5
Show file tree
Hide file tree
Showing 19 changed files with 87 additions and 65 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
"eslint-plugin-svelte": "2.35.1",
"husky": "8.0.3",
"jsdom": "23.0.1",
"lossless-json": "3.0.2",
"lossless-json": "4.0.1",
"npm-run-all": "4.1.5",
"prettier": "3.1.0",
"prettier-plugin-svelte": "3.1.2",
Expand Down
3 changes: 2 additions & 1 deletion src/lib/components/JSONEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@
debug('parser changed, recreate editor')
if (isJSONContent(content)) {
const text = previousParser.stringify(content.json)
content = {
json: parser.parse(previousParser.stringify(content.json))
json: text !== undefined ? parser.parse(text) : undefined
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/modals/TransformWizard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
$: projectionPaths =
queryOptions?.projection?.paths && projectionOptions
? queryOptions.projection.paths
? (queryOptions.projection.paths
.map((path) => projectionOptions.find((option) => isEqual(option.value, path)))
.filter((option) => !!option) as PathOption[]
.filter((option) => !!option) as PathOption[])
: null
function changeFilterPath(path: JSONPath | undefined) {
Expand Down Expand Up @@ -100,9 +100,9 @@
}
$: changeFilterPath(filterPath?.value)
$: changeFilterRelation(filterRelation?.value )
$: changeFilterRelation(filterRelation?.value)
$: changeFilterValue(filterValue)
$: changeSortPath(sortPath?.value )
$: changeSortPath(sortPath?.value)
$: changeSortDirection(sortDirection?.value)
$: changeProjectionPaths(projectionPaths ? projectionPaths.map((item) => item.value) : undefined)
</script>
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/modes/tablemode/tag/InlineValue.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<svelte:options immutable={true} />

<script lang="ts">
import type { JSONArray, JSONObject, JSONPath } from 'immutable-json-patch'
import type { JSONPath } from 'immutable-json-patch'
import type { JSONParser } from '$lib/types'
import { truncate } from '$lib/utils/stringUtils.js'
import { MAX_INLINE_OBJECT_CHARS } from '$lib/constants.js'
export let path: JSONPath
export let value: JSONArray | JSONObject
export let value: unknown
export let parser: JSONParser
export let isSelected: boolean
export let onEdit: (path: JSONPath) => void
Expand All @@ -19,7 +19,7 @@
class:jse-selected={isSelected}
on:dblclick={() => onEdit(path)}
>
{truncate(parser.stringify(value), MAX_INLINE_OBJECT_CHARS)}
{truncate(parser.stringify(value) ?? '', MAX_INLINE_OBJECT_CHARS)}
</button>

<style src="./InlineValue.scss"></style>
8 changes: 4 additions & 4 deletions src/lib/components/modes/textmode/StatusBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
export let editorState: EditorState | undefined
let pos: number
let pos: number | undefined
$: pos = editorState?.selection?.main?.head
let line: Line
$: line = editorState?.doc?.lineAt(pos)
let line: Line | undefined
$: line = pos ? editorState?.doc?.lineAt(pos) : undefined
let lineNumber: number | undefined
$: lineNumber = line ? line.number : undefined
let columnNumber: number | undefined
$: columnNumber = line ? pos - line.from + 1 : undefined
$: columnNumber = line !==undefined && pos !== undefined ? pos - line.from + 1 : undefined
let charCount: number | undefined
$: charCount = editorState?.selection?.ranges?.reduce((count, range) => {
Expand Down
30 changes: 17 additions & 13 deletions src/lib/components/modes/treemode/TreeMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@
} from '$lib/logic/actions.js'
import JSONPreview from '../../controls/JSONPreview.svelte'
import type { Context } from 'svelte-simple-modal'
import { isJSONContent, isTextContent } from '$lib/utils/jsonUtils.js'
const debug = createDebug('jsoneditor:TreeMode')
Expand Down Expand Up @@ -488,11 +489,9 @@
}
function applyExternalContent(updatedContent: Content) {
if (updatedContent.json !== undefined) {
if (isJSONContent(updatedContent)) {
applyExternalJson(updatedContent.json)
}
if (updatedContent.text !== undefined) {
} else if (isTextContent(updatedContent)) {
applyExternalText(updatedContent.text)
}
}
Expand Down Expand Up @@ -542,7 +541,7 @@
}
function applyExternalText(updatedText: string | undefined) {
if (updatedText === undefined || externalContent['json'] !== undefined) {
if (updatedText === undefined || isJSONContent(externalContent)) {
return
}
Expand Down Expand Up @@ -896,7 +895,10 @@
function handlePaste(event: ClipboardEvent) {
event.preventDefault()
const clipboardText = event.clipboardData.getData('text/plain')
const clipboardText = event.clipboardData?.getData('text/plain')
if (clipboardText == null) {
return
}
onPaste({
clipboardText,
Expand Down Expand Up @@ -1096,7 +1098,7 @@
debug('insert before', { selection: documentState.selection, selectionBefore, parentPath })
tick().then(handleContextMenu)
tick().then(() => handleContextMenu())
}
function handleInsertAfter() {
Expand All @@ -1110,7 +1112,7 @@
updateSelection(createAfterSelection(path))
tick().then(handleContextMenu)
tick().then(() => handleContextMenu())
}
async function handleInsertCharacter(char: string) {
Expand Down Expand Up @@ -1792,10 +1794,12 @@
}
}
function handleMouseDown(event: MouseEvent & { target: HTMLDivElement }) {
function handleMouseDown(event: Event) {
debug('handleMouseDown', event)
if (!isChildOfNodeName(event.target, 'BUTTON') && !event.target.isContentEditable) {
const target = event.target as HTMLElement
if (!isChildOfNodeName(target, 'BUTTON') && !target.isContentEditable) {
// for example when clicking on the empty area in the main menu
focus()
Expand Down Expand Up @@ -1866,7 +1870,7 @@
})
}
function handleContextMenu(event: MouseEvent & { currentTarget: EventTarget & HTMLDivElement }) {
function handleContextMenu(event?: Event) {
if (readOnly || isEditingSelection(documentState.selection)) {
return
}
Expand Down Expand Up @@ -1915,13 +1919,13 @@
return false
}
function handleContextMenuFromTreeMenu(event) {
function handleContextMenuFromTreeMenu(event: Event) {
if (readOnly) {
return
}
openContextMenu({
anchor: findParentWithNodeName(event.target, 'BUTTON'),
anchor: findParentWithNodeName(event.target as HTMLElement, 'BUTTON'),
offsetTop: 0,
width: CONTEXT_MENU_WIDTH,
height: CONTEXT_MENU_HEIGHT,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/logic/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ export function onInsert({
const newValue = createNewValue(json, selection, insertType)

if (json !== undefined) {
const data = parser.stringify(newValue)
const data = parser.stringify(newValue) as string
const operations = insert(json, selection, data, parser)
debug('onInsert', { insertType, operations, newValue, data })

Expand Down
2 changes: 1 addition & 1 deletion src/lib/logic/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ export function createNewValue(
json: unknown | undefined,
selection: JSONSelection | null,
valueType: 'object' | 'array' | 'structure' | 'value'
) {
): unknown {
if (valueType === 'object') {
return {}
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/logic/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,13 +658,13 @@ export function selectionToPartialJson(

if (isValueSelection(selection)) {
const value = getIn(json, selection.path)
return typeof value === 'string' ? value : parser.stringify(value, null, indentation) // TODO: customizable indentation?
return typeof value === 'string' ? value : parser.stringify(value, null, indentation) ?? null // TODO: customizable indentation?
}

if (isMultiSelection(selection)) {
if (isEmpty(selection.focusPath)) {
// root object -> does not have a parent key/index
return parser.stringify(json, null, indentation)
return parser.stringify(json, null, indentation) ?? null
}

const parentPath = getParentPath(selection)
Expand All @@ -673,7 +673,7 @@ export function selectionToPartialJson(
if (isMultiSelectionWithOneItem(selection)) {
// do not suffix a single selected array item with a comma
const item = getIn(json, selection.focusPath)
return parser.stringify(item, null, indentation)
return parser.stringify(item, null, indentation) ?? null
} else {
return getSelectionPaths(json, selection)
.map((path) => {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/logic/validation.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { test, describe } from 'vitest'
import { deepStrictEqual } from 'assert'
import { mapValidationErrors, validateJSON, validateText } from './validation.js'
import type { JSONParser, ValidationError } from '$lib/types'
import type { ValidationError } from '$lib/types'
import { ValidationSeverity } from '$lib/types.js'
import { stringify, parse, isLosslessNumber } from 'lossless-json'
import { LosslessNumber } from 'lossless-json'

const LosslessJSONParser = { parse, stringify } as JSONParser
const LosslessJSONParser = { parse, stringify }

describe('validation', () => {
test('should create a map from a list with validation errors', () => {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/logic/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export function validateJSON(
if (parser !== validationParser) {
// if needed, convert for example Lossless JSON to native JSON
// (like replace bigint or LosslessNumber into regular numbers)
const convertedJSON = validationParser.parse(parser.stringify(json))
const text = parser.stringify(json)
const convertedJSON = text !== undefined ? validationParser.parse(text) : undefined
return validator(convertedJSON)
} else {
return validator(json)
Expand Down
3 changes: 1 addition & 2 deletions src/lib/plugins/query/jmespathQueryLanguage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { describe, test } from 'vitest'
import { jmespathQueryLanguage } from './jmespathQueryLanguage.js'
import { cloneDeep } from 'lodash-es'
import { LosslessNumber, parse, stringify } from 'lossless-json'
import type { JSONParser } from '$lib/types'

const { createQuery, executeQuery } = jmespathQueryLanguage

Expand Down Expand Up @@ -238,7 +237,7 @@ describe('jmespathQueryLanguage', () => {
})

test('should work with alternative parsers and non-native JSON data types', () => {
const LosslessJSONParser = { parse, stringify } as JSONParser
const LosslessJSONParser = { parse, stringify }

const data = [new LosslessNumber('4'), new LosslessNumber('7'), new LosslessNumber('5')]
const query = createQuery(data, {
Expand Down
8 changes: 7 additions & 1 deletion src/lib/plugins/query/jmespathQueryLanguage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,13 @@ function createQuery(json: unknown, queryOptions: QueryLanguageOptions): string
*/
function executeQuery(json: unknown, query: string, parser: JSONParser): unknown {
// JMESPath cannot handle non-native JSON data types like LosslessNumber
const preprocessedJson = isEqualParser(parser, JSON) ? json : JSON.parse(parser.stringify(json))

function stringifyAndParse(json: unknown) {
const text = parser.stringify(json)
return text !== undefined ? JSON.parse(text) : undefined
}

const preprocessedJson = isEqualParser(parser, JSON) ? json : stringifyAndParse(json)

return jmespath.search(preprocessedJson, query)
}
Expand Down
3 changes: 1 addition & 2 deletions src/lib/plugins/query/lodashQueryLanguage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import assert from 'assert'
import { lodashQueryLanguage } from './lodashQueryLanguage.js'
import { cloneDeep } from 'lodash-es'
import { LosslessNumber, parse, stringify } from 'lossless-json'
import type { JSONParser } from '$lib/types'

const { createQuery, executeQuery } = lodashQueryLanguage

Expand Down Expand Up @@ -361,7 +360,7 @@ describe('lodashQueryLanguage', () => {
})

test('should work with alternative parsers and non-native JSON data types', () => {
const LosslessJSONParser = { parse, stringify } as JSONParser
const LosslessJSONParser = { parse, stringify }

const data = [new LosslessNumber('4'), new LosslessNumber('7'), new LosslessNumber('5')]
const query = createQuery(data, {
Expand Down
23 changes: 20 additions & 3 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ import type { JSONPatchDocument, JSONPath, JSONPointer } from 'immutable-json-pa
import type { SvelteComponent } from 'svelte'
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'

export type TextContent = { text: string } | { json: undefined; text: string }
export type TextContent = { text: string }

export type JSONContent = { json: unknown } | { json: unknown; text: undefined }
export type JSONContent = { json: unknown }

export type Content = JSONContent | TextContent

export type JSONParser = JSON
// The `JSONParser` interface is compatible with `JSON`,
// except that JSON.stringify is wrongly defined to return a string whilst it can return a string or undefined
// see: https://stackoverflow.com/questions/74461780/is-the-official-type-definition-for-json-stringify-wrong
export interface JSONParser {
parse(
text: string,
reviver?: ((this: unknown, key: string, value: unknown) => unknown) | null
): unknown

stringify(
value: unknown,
replacer?:
| ((this: unknown, key: string, value: unknown) => unknown)
| Array<number | string>
| null,
space?: string | number
): string | undefined
}

export interface JSONPathParser {
parse: (pathStr: string) => JSONPath
Expand Down
Loading

0 comments on commit 217d6d5

Please sign in to comment.