Refactored ESON to use Symbols, refactored patchEson
This commit is contained in:
parent
53b20e2f59
commit
156f330e4e
|
@ -8,7 +8,7 @@ import FloatingMenu from './menu/FloatingMenu'
|
|||
import { escapeHTML, unescapeHTML } from '../utils/stringUtils'
|
||||
import { getInnerText, insideRect, findParentWithAttribute } from '../utils/domUtils'
|
||||
import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
|
||||
import { compileJSONPointer, mapEsonArray, SELECTED, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE } from '../eson'
|
||||
import { compileJSONPointer, META, SELECTED, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE } from '../eson'
|
||||
|
||||
import type { ESONObjectProperty, ESON, SearchResultStatus, Path } from '../types'
|
||||
|
||||
|
@ -46,10 +46,10 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
if (this.props.eson._meta.type === 'Object') {
|
||||
if (this.props.eson[META].type === 'Object') {
|
||||
return this.renderJSONObject(this.props)
|
||||
}
|
||||
else if (this.props.eson._meta.type === 'Array') {
|
||||
else if (this.props.eson[META].type === 'Array') {
|
||||
return this.renderJSONArray(this.props)
|
||||
}
|
||||
else { // no Object or Array
|
||||
|
@ -58,7 +58,7 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
|
||||
renderJSONObject ({prop, index, eson, options, events}) {
|
||||
const keys = eson._meta.keys
|
||||
const keys = eson[META].keys
|
||||
const node = h('div', {
|
||||
key: 'node',
|
||||
onKeyDown: this.handleKeyDown,
|
||||
|
@ -70,14 +70,14 @@ export default class JSONNode extends PureComponent {
|
|||
this.renderProperty(prop, index, eson, options),
|
||||
this.renderReadonly(`{${keys.length}}`, `Array containing ${keys.length} items`),
|
||||
// this.renderFloatingMenuButton(),
|
||||
this.renderError(eson._meta.error)
|
||||
this.renderError(eson[META].error)
|
||||
])
|
||||
|
||||
let childs
|
||||
if (eson._meta.expanded) {
|
||||
if (eson[META].expanded) {
|
||||
if (keys.length > 0) {
|
||||
const props = keys.map(key => h(this.constructor, {
|
||||
key: eson[key]._meta.id,
|
||||
key: eson[key][META].id,
|
||||
// parent: this,
|
||||
prop: key,
|
||||
eson: eson[key],
|
||||
|
@ -94,7 +94,7 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const floatingMenu = (eson._meta.selected === SELECTED_END)
|
||||
const floatingMenu = (eson[META].selected === SELECTED_END)
|
||||
? this.renderFloatingMenu([
|
||||
{type: 'sort'},
|
||||
{type: 'duplicate'},
|
||||
|
@ -108,8 +108,8 @@ export default class JSONNode extends PureComponent {
|
|||
const insertArea = this.renderInsertBeforeArea()
|
||||
|
||||
return h('div', {
|
||||
'data-path': compileJSONPointer(this.props.eson._meta.path),
|
||||
className: this.getContainerClassName(eson._meta.selected, this.state.hover),
|
||||
'data-path': compileJSONPointer(this.props.eson[META].path),
|
||||
className: this.getContainerClassName(eson[META].selected, this.state.hover),
|
||||
onMouseOver: this.handleMouseOver,
|
||||
onMouseLeave: this.handleMouseLeave
|
||||
}, [node, floatingMenu, insertArea, childs])
|
||||
|
@ -125,16 +125,16 @@ export default class JSONNode extends PureComponent {
|
|||
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
||||
// this.renderActionMenuButton(),
|
||||
this.renderProperty(prop, index, eson, options),
|
||||
this.renderReadonly(`[${eson._meta.length}]`, `Array containing ${eson._meta.length} items`),
|
||||
this.renderReadonly(`[${eson.length}]`, `Array containing ${eson.length} items`),
|
||||
// this.renderFloatingMenuButton(),
|
||||
this.renderError(eson._meta.error)
|
||||
this.renderError(eson[META].error)
|
||||
])
|
||||
|
||||
let childs
|
||||
if (eson._meta.expanded) {
|
||||
if (eson._meta.length > 0) {
|
||||
const items = mapEsonArray(eson, (item, index) => h(this.constructor, {
|
||||
key : item._meta.id,
|
||||
if (eson[META].expanded) {
|
||||
if (eson.length > 0) {
|
||||
const items = eson.map((item, index) => h(this.constructor, {
|
||||
key : item[META].id,
|
||||
// parent: this,
|
||||
index,
|
||||
eson: item,
|
||||
|
@ -151,7 +151,7 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const floatingMenu = (eson._meta.selected === SELECTED_END)
|
||||
const floatingMenu = (eson[META].selected === SELECTED_END)
|
||||
? this.renderFloatingMenu([
|
||||
{type: 'sort'},
|
||||
{type: 'duplicate'},
|
||||
|
@ -165,8 +165,8 @@ export default class JSONNode extends PureComponent {
|
|||
const insertArea = this.renderInsertBeforeArea()
|
||||
|
||||
return h('div', {
|
||||
'data-path': compileJSONPointer(this.props.eson._meta.path),
|
||||
className: this.getContainerClassName(eson._meta.selected, this.state.hover),
|
||||
'data-path': compileJSONPointer(this.props.eson[META].path),
|
||||
className: this.getContainerClassName(eson[META].selected, this.state.hover),
|
||||
onMouseOver: this.handleMouseOver,
|
||||
onMouseLeave: this.handleMouseLeave
|
||||
}, [node, floatingMenu, insertArea, childs])
|
||||
|
@ -183,12 +183,12 @@ export default class JSONNode extends PureComponent {
|
|||
// this.renderActionMenuButton(),
|
||||
this.renderProperty(prop, index, eson, options),
|
||||
this.renderSeparator(),
|
||||
this.renderValue(eson._meta.value, eson._meta.searchValue, options),
|
||||
this.renderValue(eson[META].value, eson[META].searchValue, options),
|
||||
// this.renderFloatingMenuButton(),
|
||||
this.renderError(eson._meta.error)
|
||||
this.renderError(eson[META].error)
|
||||
])
|
||||
|
||||
const floatingMenu = (eson._meta.selected === SELECTED_END)
|
||||
const floatingMenu = (eson[META].selected === SELECTED_END)
|
||||
? this.renderFloatingMenu([
|
||||
// {text: 'String', onClick: this.props.events.onChangeType, type: 'checkbox', checked: false},
|
||||
{type: 'duplicate'},
|
||||
|
@ -202,15 +202,15 @@ export default class JSONNode extends PureComponent {
|
|||
const insertArea = this.renderInsertBeforeArea()
|
||||
|
||||
return h('div', {
|
||||
'data-path': compileJSONPointer(this.props.eson._meta.path),
|
||||
className: this.getContainerClassName(eson._meta.selected, this.state.hover),
|
||||
'data-path': compileJSONPointer(this.props.eson[META].path),
|
||||
className: this.getContainerClassName(eson[META].selected, this.state.hover),
|
||||
onMouseOver: this.handleMouseOver,
|
||||
onMouseLeave: this.handleMouseLeave
|
||||
}, [node, floatingMenu, insertArea])
|
||||
}
|
||||
|
||||
renderInsertBeforeArea () {
|
||||
const floatingMenu = (this.props.eson._meta.selected === SELECTED_BEFORE)
|
||||
const floatingMenu = (this.props.eson[META].selected === SELECTED_BEFORE)
|
||||
? this.renderFloatingMenu([
|
||||
{type: 'insertStructure'},
|
||||
{type: 'insertValue'},
|
||||
|
@ -234,7 +234,7 @@ export default class JSONNode extends PureComponent {
|
|||
*/
|
||||
renderAppend (text) {
|
||||
return h('div', {
|
||||
'data-path': compileJSONPointer(this.props.eson._meta.path) + '/-',
|
||||
'data-path': compileJSONPointer(this.props.eson[META].path) + '/-',
|
||||
className: 'jsoneditor-node',
|
||||
onKeyDown: this.handleKeyDownAppend
|
||||
}, [
|
||||
|
@ -269,10 +269,10 @@ export default class JSONNode extends PureComponent {
|
|||
}, rootName)
|
||||
}
|
||||
|
||||
const editable = !isIndex && (!options.isPropertyEditable || options.isPropertyEditable(this.props.eson._meta.path))
|
||||
const editable = !isIndex && (!options.isPropertyEditable || options.isPropertyEditable(this.props.eson[META].path))
|
||||
|
||||
const emptyClassName = (prop != null && prop.length === 0) ? ' jsoneditor-empty' : ''
|
||||
const searchClassName = prop != null ? JSONNode.getSearchResultClass(eson._meta.searchProperty) : ''
|
||||
const searchClassName = prop != null ? JSONNode.getSearchResultClass(eson[META].searchProperty) : ''
|
||||
const escapedPropName = prop != null ? escapeHTML(prop, options.escapeUnicode) : null
|
||||
|
||||
if (editable) {
|
||||
|
@ -303,7 +303,7 @@ export default class JSONNode extends PureComponent {
|
|||
const itsAnUrl = isUrl(value)
|
||||
const isEmpty = escapedValue.length === 0
|
||||
|
||||
const editable = !options.isValueEditable || options.isValueEditable(this.props.eson._meta.path)
|
||||
const editable = !options.isValueEditable || options.isValueEditable(this.props.eson[META].path)
|
||||
if (editable) {
|
||||
return h('div', {
|
||||
key: 'value',
|
||||
|
@ -395,7 +395,7 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
|
||||
target.className = JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
|
||||
JSONNode.getSearchResultClass(this.props.eson._meta.searchValue)
|
||||
JSONNode.getSearchResultClass(this.props.eson[META].searchValue)
|
||||
target.title = itsAnUrl ? JSONNode.URL_TITLE : ''
|
||||
|
||||
// remove all classNames from childs (needed for IE and Edge)
|
||||
|
@ -448,7 +448,7 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
|
||||
renderExpandButton () {
|
||||
const className = `jsoneditor-button jsoneditor-${this.props.eson._meta.expanded ? 'expanded' : 'collapsed'}`
|
||||
const className = `jsoneditor-button jsoneditor-${this.props.eson[META].expanded ? 'expanded' : 'collapsed'}`
|
||||
|
||||
return h('div', {key: 'expand', className: 'jsoneditor-button-container'},
|
||||
h('button', {
|
||||
|
@ -469,9 +469,9 @@ export default class JSONNode extends PureComponent {
|
|||
|
||||
return h(ActionMenu, {
|
||||
key: 'menu',
|
||||
path: this.props.eson._meta.path,
|
||||
path: this.props.eson[META].path,
|
||||
events: this.props.events,
|
||||
type: this.props.eson._meta.type, // TODO: fix type
|
||||
type: this.props.eson[META].type, // TODO: fix type
|
||||
|
||||
menuType,
|
||||
open: true,
|
||||
|
@ -513,7 +513,7 @@ export default class JSONNode extends PureComponent {
|
|||
renderFloatingMenu (items) {
|
||||
return h(FloatingMenu, {
|
||||
key: 'floating-menu',
|
||||
path: this.props.eson._meta.path,
|
||||
path: this.props.eson[META].path,
|
||||
events: this.props.events,
|
||||
items
|
||||
})
|
||||
|
@ -603,7 +603,7 @@ export default class JSONNode extends PureComponent {
|
|||
|
||||
/** @private */
|
||||
handleChangeProperty = (event) => {
|
||||
const parentPath = initial(this.props.eson._meta.path)
|
||||
const parentPath = initial(this.props.eson[META].path)
|
||||
const oldProp = this.props.prop.name
|
||||
const newProp = unescapeHTML(getInnerText(event.target))
|
||||
|
||||
|
@ -616,8 +616,8 @@ export default class JSONNode extends PureComponent {
|
|||
handleChangeValue = (event) => {
|
||||
const value = this.getValueFromEvent(event)
|
||||
|
||||
if (value !== this.props.eson._meta.value) {
|
||||
this.props.events.onChangeValue(this.props.eson._meta.path, value)
|
||||
if (value !== this.props.eson[META].value) {
|
||||
this.props.events.onChangeValue(this.props.eson[META].path, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -634,24 +634,24 @@ export default class JSONNode extends PureComponent {
|
|||
|
||||
if (keyBinding === 'duplicate') {
|
||||
event.preventDefault()
|
||||
this.props.events.onDuplicate(this.props.eson._meta.path)
|
||||
this.props.events.onDuplicate(this.props.eson[META].path)
|
||||
}
|
||||
|
||||
if (keyBinding === 'insert') {
|
||||
event.preventDefault()
|
||||
this.props.events.onInsert(this.props.eson._meta.path, 'value')
|
||||
this.props.events.onInsert(this.props.eson[META].path, 'value')
|
||||
}
|
||||
|
||||
if (keyBinding === 'remove') {
|
||||
event.preventDefault()
|
||||
this.props.events.onRemove(this.props.eson._meta.path)
|
||||
this.props.events.onRemove(this.props.eson[META].path)
|
||||
}
|
||||
|
||||
if (keyBinding === 'expand') {
|
||||
event.preventDefault()
|
||||
const recurse = false
|
||||
const expanded = !this.props.eson._meta.expanded
|
||||
this.props.events.onExpand(this.props.eson._meta.path, expanded, recurse)
|
||||
const expanded = !this.props.eson[META].expanded
|
||||
this.props.events.onExpand(this.props.eson[META].path, expanded, recurse)
|
||||
}
|
||||
|
||||
if (keyBinding === 'actionMenu') {
|
||||
|
@ -666,7 +666,7 @@ export default class JSONNode extends PureComponent {
|
|||
|
||||
if (keyBinding === 'insert') {
|
||||
event.preventDefault()
|
||||
this.props.events.onAppend(this.props.eson._meta.path, 'value')
|
||||
this.props.events.onAppend(this.props.eson[META].path, 'value')
|
||||
}
|
||||
|
||||
if (keyBinding === 'actionMenu') {
|
||||
|
@ -687,8 +687,8 @@ export default class JSONNode extends PureComponent {
|
|||
/** @private */
|
||||
handleExpand = (event) => {
|
||||
const recurse = event.ctrlKey
|
||||
const path = this.props.eson._meta.path
|
||||
const expanded = !this.props.eson._meta.expanded
|
||||
const path = this.props.eson[META].path
|
||||
const expanded = !this.props.eson[META].expanded
|
||||
|
||||
this.props.events.onExpand(path, expanded, recurse)
|
||||
}
|
||||
|
@ -717,7 +717,7 @@ export default class JSONNode extends PureComponent {
|
|||
*/
|
||||
getValueFromEvent (event) {
|
||||
const stringValue = unescapeHTML(getInnerText(event.target))
|
||||
return this.props.eson._meta.type === 'string'
|
||||
return this.props.eson[META].type === 'string'
|
||||
? stringValue
|
||||
: stringConvert(stringValue)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { getIn, setIn, updateIn } from '../utils/immutabilityHelpers'
|
|||
import { parseJSON } from '../utils/jsonUtils'
|
||||
import { enrichSchemaError } from '../utils/schemaUtils'
|
||||
import {
|
||||
META,
|
||||
jsonToEson, esonToJson, pathExists,
|
||||
expand, expandOne, expandPath, applyErrors,
|
||||
search, nextSearchResult, previousSearchResult,
|
||||
|
@ -154,7 +155,7 @@ export default class TreeMode extends Component {
|
|||
|
||||
// Apply json
|
||||
if (nextProps.json !== currentProps.json) {
|
||||
// FIXME: merge _meta from existing eson
|
||||
// FIXME: merge meta data from existing eson
|
||||
this.setState({
|
||||
json: nextProps.json,
|
||||
eson: jsonToEson(nextProps.json) // FIXME: how to handle expand?
|
||||
|
@ -222,7 +223,7 @@ export default class TreeMode extends Component {
|
|||
onMouseDown: this.handleTouchStart,
|
||||
onTouchStart: this.handleTouchStart,
|
||||
className: 'jsoneditor-list jsoneditor-root' +
|
||||
(eson._meta.selected ? ' jsoneditor-selected' : '')},
|
||||
(eson[META].selected ? ' jsoneditor-selected' : '')},
|
||||
h(Node, {
|
||||
eson,
|
||||
events: state.events,
|
||||
|
@ -982,7 +983,7 @@ export default class TreeMode extends Component {
|
|||
* @return {boolean} Returns true when expanded, false otherwise
|
||||
*/
|
||||
isExpanded (path) {
|
||||
return getIn(this.state.eson, path)._meta.expanded
|
||||
return getIn(this.state.eson, path)[META].expanded
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
98
src/eson.js
98
src/eson.js
|
@ -47,7 +47,7 @@ export function jsonToEson (json, path = []) {
|
|||
return eson
|
||||
}
|
||||
else if (Array.isArray(json)) {
|
||||
let eson = json.map((value, index) => jsonToEson(value, path.concat(index)))
|
||||
let eson = json.map((value, index) => jsonToEson(value, path.concat(String(index))))
|
||||
eson[META] = { id, path, type: 'Array' }
|
||||
return eson
|
||||
}
|
||||
|
@ -58,21 +58,6 @@ export function jsonToEson (json, path = []) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map over an eson array
|
||||
* @param {ESONArray} esonArray
|
||||
* @param {function (value, index, array)} callback
|
||||
* @return {Array}
|
||||
*/
|
||||
export function mapEsonArray (esonArray, callback) {
|
||||
const length = esonArray[META].length
|
||||
let result = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
result[i] = callback(esonArray[i], i, esonArray)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand function which will expand all nodes
|
||||
* @param {Path} path
|
||||
|
@ -130,21 +115,21 @@ export function jsonToEsonOld (json, expand = expandAll, path: JSONPath = [], ty
|
|||
* @return {Object | Array | string | number | boolean | null} json
|
||||
*/
|
||||
export function esonToJson (eson: ESON) {
|
||||
switch (eson.type) {
|
||||
switch (eson[META].type) {
|
||||
case 'Array':
|
||||
return eson.items.map(item => esonToJson(item.value))
|
||||
return eson.map(item => esonToJson(item))
|
||||
|
||||
case 'Object':
|
||||
const object = {}
|
||||
|
||||
eson.props.forEach(prop => {
|
||||
object[prop.name] = esonToJson(prop.value)
|
||||
eson[META].keys.forEach(prop => {
|
||||
object[prop] = esonToJson(eson[prop])
|
||||
})
|
||||
|
||||
return object
|
||||
|
||||
default: // type 'string' or 'value'
|
||||
return eson.value
|
||||
return eson[META].value
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,13 +221,6 @@ export function setInEson (eson: ESON, jsonPath: JSONPath, value: JSONType) {
|
|||
return setIn(eson, toEsonPath(eson, jsonPath), value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a nested property in an ESON object using a JSON path
|
||||
*/
|
||||
export function updateInEson (eson: ESON, jsonPath: JSONPath, callback) {
|
||||
return updateIn(eson, toEsonPath(eson, jsonPath), callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a nested property in an ESON object using a JSON path
|
||||
*/
|
||||
|
@ -279,7 +257,7 @@ export function transform (eson, callback, path = []) {
|
|||
let changed = false
|
||||
let updatedArr = []
|
||||
for (let i = 0; i < updated.length; i++) {
|
||||
updatedArr[i] = transform(updated[i], callback, path.concat(i))
|
||||
updatedArr[i] = transform(updated[i], callback, path.concat(String(i)))
|
||||
changed = changed || (updatedArr[i] !== updated[i])
|
||||
}
|
||||
updatedArr[META] = updated[META]
|
||||
|
@ -290,6 +268,26 @@ export function transform (eson, callback, path = []) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively update all paths in an eson object, array or value
|
||||
* @param {ESON} eson
|
||||
* @param {Path} [path]
|
||||
* @return {ESON}
|
||||
*/
|
||||
export function updatePaths(eson, path = []) {
|
||||
return transform(eson, function (value, path) {
|
||||
if (!isEqual(value[META].path, path)) {
|
||||
// TODO: extend setIn to support symbols
|
||||
let updatedValue = cloneWithSymbols(value)
|
||||
updatedValue[META] = setIn(value[META], ['path'], path)
|
||||
return updatedValue
|
||||
}
|
||||
else {
|
||||
return value
|
||||
}
|
||||
}, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand or collapse all items matching a filter callback
|
||||
* @param {ESON} eson
|
||||
|
@ -550,7 +548,7 @@ export function applySelection (eson, selection) {
|
|||
const maxIndex = Math.max(startIndex, endIndex) + 1 // include max index itself
|
||||
|
||||
const selectedIndices = range(minIndex, maxIndex)
|
||||
selectedPaths = selectedIndices.map(index => rootPath.concat(index))
|
||||
selectedPaths = selectedIndices.map(index => rootPath.concat(String(index)))
|
||||
|
||||
let updatedArr = root.slice()
|
||||
updatedArr = cloneWithSymbols(root)
|
||||
|
@ -752,11 +750,11 @@ function recurseTraverse (value: ESON, path: JSONPath, root: ESON, callback: Rec
|
|||
/**
|
||||
* Test whether a path exists in the eson object
|
||||
* @param {ESON} eson
|
||||
* @param {JSONPath} path
|
||||
* @param {Path} path
|
||||
* @return {boolean} Returns true if the path exists, else returns false
|
||||
* @private
|
||||
*/
|
||||
export function pathExists (eson: ESON, path: JSONPath) {
|
||||
export function pathExists (eson, path) {
|
||||
if (eson === undefined) {
|
||||
return false
|
||||
}
|
||||
|
@ -765,19 +763,13 @@ export function pathExists (eson: ESON, path: JSONPath) {
|
|||
return true
|
||||
}
|
||||
|
||||
if (eson.type === 'Array') {
|
||||
if (Array.isArray(eson)) {
|
||||
// index of an array
|
||||
const index = path[0]
|
||||
const item = eson.items[parseInt(index)]
|
||||
|
||||
return pathExists(item && item.value, path.slice(1))
|
||||
return pathExists(eson[parseInt(path[0])], path.slice(1))
|
||||
}
|
||||
else { // eson.type === 'Object'
|
||||
// object property. find the index of this property
|
||||
const index = findPropertyIndex(eson, path[0])
|
||||
const prop = eson.props[index]
|
||||
|
||||
return pathExists(prop && prop.value, path.slice(1))
|
||||
return pathExists(eson[path[0]], path.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -790,15 +782,12 @@ export function pathExists (eson: ESON, path: JSONPath) {
|
|||
*/
|
||||
export function resolvePathIndex (eson, path) {
|
||||
if (path[path.length - 1] === '-') {
|
||||
const parentPath = path.slice(0, path.length - 1)
|
||||
const parent = getInEson(eson, parentPath)
|
||||
const parentPath = initial(path)
|
||||
const parent = getIn(eson, parentPath)
|
||||
|
||||
if (parent.type === 'Array') {
|
||||
const index = parent.items.length
|
||||
const resolvedPath = path.slice(0)
|
||||
resolvedPath[resolvedPath.length - 1] = index
|
||||
|
||||
return resolvedPath
|
||||
if (Array.isArray(parent)) {
|
||||
const index = parent.length
|
||||
return parentPath.concat(String(index))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -812,14 +801,9 @@ export function resolvePathIndex (eson, path) {
|
|||
* @return {string | null} Returns the name of the next property,
|
||||
* or null if there is none
|
||||
*/
|
||||
export function findNextProp (parent: ESONObject, prop: string) : string | null {
|
||||
const index = findPropertyIndex(parent, prop)
|
||||
if (index === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const next = parent.props[index + 1]
|
||||
return next && next.name || null
|
||||
export function findNextProp (parent, prop) {
|
||||
const index = parent[META].keys.indexOf(prop)
|
||||
return parent[META].keys[index + 1] || null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
133
src/patchEson.js
133
src/patchEson.js
|
@ -3,12 +3,15 @@ import initial from 'lodash/initial'
|
|||
import last from 'lodash/last'
|
||||
|
||||
import type { ESON, Path, ESONPatch } from './types'
|
||||
import { setIn, updateIn, getIn, insertAt } from './utils/immutabilityHelpers'
|
||||
import {
|
||||
jsonToEson, jsonToEsonOld, esonToJson, toEsonPath,
|
||||
getInEson, setInEson, deleteInEson,
|
||||
setIn, updateIn, getIn, deleteIn, insertAt,
|
||||
cloneWithSymbols
|
||||
} from './utils/immutabilityHelpers'
|
||||
import {
|
||||
META,
|
||||
jsonToEson, esonToJson, updatePaths,
|
||||
parseJSONPointer, compileJSONPointer,
|
||||
expandAll, pathExists, resolvePathIndex, findPropertyIndex, findNextProp, createId
|
||||
expandAll, pathExists, resolvePathIndex, createId
|
||||
} from './eson'
|
||||
|
||||
/**
|
||||
|
@ -19,7 +22,7 @@ import {
|
|||
* what nodes must be expanded
|
||||
* @return {{data: ESON, revert: Object[], error: Error | null}}
|
||||
*/
|
||||
export function patchEson (eson: ESON, patch: ESONPatch, expand = expandAll) {
|
||||
export function patchEson (eson, patch, expand = expandAll) {
|
||||
let updatedEson = eson
|
||||
let revert = []
|
||||
|
||||
|
@ -32,7 +35,9 @@ export function patchEson (eson: ESON, patch: ESONPatch, expand = expandAll) {
|
|||
switch (action.op) {
|
||||
case 'add': {
|
||||
const path = parseJSONPointer(action.path)
|
||||
const newValue = jsonToEsonOld(action.value, expand, path, options && options.type)
|
||||
const newValue = jsonToEson(action.value, path)
|
||||
// FIXME: apply expanded state
|
||||
// FIXME: apply options.type
|
||||
const result = add(updatedEson, action.path, newValue, options)
|
||||
updatedEson = result.data
|
||||
revert = result.revert.concat(revert)
|
||||
|
@ -50,7 +55,9 @@ export function patchEson (eson: ESON, patch: ESONPatch, expand = expandAll) {
|
|||
|
||||
case 'replace': {
|
||||
const path = parseJSONPointer(action.path)
|
||||
const newValue = jsonToEsonOld(action.value, expand, path, options && options.type)
|
||||
const newValue = jsonToEson(action.value, path)
|
||||
// FIXME: apply expanded state
|
||||
// FIXME: apply options.type
|
||||
const result = replace(updatedEson, path, newValue)
|
||||
updatedEson = result.data
|
||||
revert = result.revert.concat(revert)
|
||||
|
@ -125,17 +132,17 @@ export function patchEson (eson: ESON, patch: ESONPatch, expand = expandAll) {
|
|||
* @param {ESON} value
|
||||
* @return {{data: ESON, revert: ESONPatch}}
|
||||
*/
|
||||
export function replace (data: ESON, path: Path, value: ESON) {
|
||||
const oldValue = getInEson(data, path)
|
||||
export function replace (data, path, value) {
|
||||
const oldValue = getIn(data, path)
|
||||
|
||||
return {
|
||||
data: setInEson(data, path, value),
|
||||
data: setIn(data, path, value),
|
||||
revert: [{
|
||||
op: 'replace',
|
||||
path: compileJSONPointer(path),
|
||||
value: esonToJson(oldValue),
|
||||
jsoneditor: {
|
||||
type: oldValue.type
|
||||
type: oldValue[META].type
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
@ -148,40 +155,45 @@ export function replace (data: ESON, path: Path, value: ESON) {
|
|||
* @return {{data: ESON, revert: ESONPatch}}
|
||||
*/
|
||||
// FIXME: path should be a path instead of a string? (all functions in patchEson)
|
||||
export function remove (data: ESON, path: string) {
|
||||
export function remove (data, path) {
|
||||
// console.log('remove', path)
|
||||
const pathArray = parseJSONPointer(path)
|
||||
|
||||
const parentPath = initial(pathArray)
|
||||
const parent = getInEson(data, parentPath)
|
||||
const dataValue = getInEson(data, pathArray)
|
||||
const parent = getIn(data, parentPath)
|
||||
const dataValue = getIn(data, pathArray)
|
||||
const value = esonToJson(dataValue)
|
||||
|
||||
if (parent.type === 'Array') {
|
||||
if (parent[META].type === 'Array') {
|
||||
return {
|
||||
data: deleteInEson(data, pathArray),
|
||||
data: updatePaths(deleteIn(data, pathArray)),
|
||||
revert: [{
|
||||
op: 'add',
|
||||
path,
|
||||
value,
|
||||
jsoneditor: {
|
||||
type: dataValue.type
|
||||
type: dataValue[META].type
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
else { // object.type === 'Object'
|
||||
const prop = last(pathArray)
|
||||
const index = parent[META].keys.indexOf(prop)
|
||||
const nextProp = parent[META].keys[index + 1] || null
|
||||
|
||||
let updatedParent = deleteIn(parent, [prop])
|
||||
updatedParent[META] = deleteIn(parent[META], ['keys', index], parent[META].keys)
|
||||
|
||||
return {
|
||||
data: deleteInEson(data, pathArray),
|
||||
data: setIn(data, parentPath, updatePaths(updatedParent, parentPath)),
|
||||
revert: [{
|
||||
op: 'add',
|
||||
path,
|
||||
value,
|
||||
jsoneditor: {
|
||||
type: dataValue.type,
|
||||
before: findNextProp(parent, prop)
|
||||
type: dataValue[META].type,
|
||||
before: nextProp
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
@ -193,47 +205,55 @@ export function remove (data: ESON, path: string) {
|
|||
* @param {string} path
|
||||
* @param {ESON} value
|
||||
* @param {{before?: string}} [options]
|
||||
* @param {number} [id] Optional id for the new item
|
||||
* @return {{data: ESON, revert: ESONPatch}}
|
||||
* @private
|
||||
*/
|
||||
export function add (data: ESON, path: string, value: ESON, options, id = createId()) {
|
||||
// TODO: refactor path to an array with strings
|
||||
export function add (data, path, value, options) {
|
||||
// FIXME: apply id to new created values
|
||||
const pathArray = parseJSONPointer(path)
|
||||
const parentPath = pathArray.slice(0, pathArray.length - 1)
|
||||
const esonPath = toEsonPath(data, parentPath)
|
||||
const parent = getIn(data, esonPath)
|
||||
const parentPath = initial(pathArray)
|
||||
const parent = getIn(data, parentPath)
|
||||
const resolvedPath = resolvePathIndex(data, pathArray)
|
||||
const prop = resolvedPath[resolvedPath.length - 1]
|
||||
const prop = last(resolvedPath)
|
||||
|
||||
let updatedEson
|
||||
if (parent.type === 'Array') {
|
||||
const newItem = {
|
||||
id, // TODO: create a unique id within current id's instead of using a global, ever incrementing id
|
||||
value
|
||||
}
|
||||
updatedEson = insertAt(data, esonPath.concat('items', prop), newItem)
|
||||
if (parent[META].type === 'Array') {
|
||||
updatedEson = updatePaths(insertAt(data, resolvedPath, value))
|
||||
}
|
||||
else { // parent.type === 'Object'
|
||||
updatedEson = updateIn(data, esonPath, (object) => {
|
||||
const existingIndex = findPropertyIndex(object, prop)
|
||||
updatedEson = updateIn(data, parentPath, (parent) => {
|
||||
const oldValue = getIn(data, pathArray)
|
||||
const props = parent[META].keys
|
||||
const existingIndex = props.indexOf(prop)
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
// replace existing item
|
||||
return setIn(object, ['props', existingIndex, 'value'], value)
|
||||
// update path
|
||||
// FIXME: also update value's id
|
||||
let newValue = updatePaths(cloneWithSymbols(value), pathArray)
|
||||
newValue[META] = setIn(newValue[META], ['id'], oldValue[META].id)
|
||||
// console.log('copied id from existing value' + oldValue[META].id)
|
||||
|
||||
// TODO: update paths of existing value
|
||||
return setIn(parent, [prop], newValue)
|
||||
}
|
||||
else {
|
||||
// insert new item
|
||||
const newProp = { id, name: prop, value }
|
||||
const index = (options && typeof options.before === 'string')
|
||||
? findPropertyIndex(object, options.before) // insert before
|
||||
: object.props.length // append
|
||||
? props.indexOf(options.before) // insert before
|
||||
: props.length // append
|
||||
|
||||
return insertAt(object, ['props', index], newProp)
|
||||
let updatedKeys = props.slice()
|
||||
updatedKeys.splice(index, 0, prop)
|
||||
const updatedParent = setIn(parent, [prop], updatePaths(value, parentPath.concat(prop)))
|
||||
return setIn(updatedParent, [META, 'keys'], updatedKeys)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (parent.type === 'Object' && pathExists(data, resolvedPath)) {
|
||||
const oldValue = getInEson(data, resolvedPath)
|
||||
if (parent[META].type === 'Object' && pathExists(data, resolvedPath)) {
|
||||
const oldValue = getIn(data, resolvedPath)
|
||||
|
||||
return {
|
||||
data: updatedEson,
|
||||
|
@ -241,7 +261,7 @@ export function add (data: ESON, path: string, value: ESON, options, id = create
|
|||
op: 'replace',
|
||||
path: compileJSONPointer(resolvedPath),
|
||||
value: esonToJson(oldValue),
|
||||
jsoneditor: { type: oldValue.type }
|
||||
jsoneditor: { type: oldValue[META].type }
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -265,10 +285,14 @@ export function add (data: ESON, path: string, value: ESON, options, id = create
|
|||
* @return {{data: ESON, revert: ESONPatch}}
|
||||
* @private
|
||||
*/
|
||||
export function copy (data: ESON, path: string, from: string, options) {
|
||||
const value = getInEson(data, parseJSONPointer(from))
|
||||
export function copy (data, path, from, options) {
|
||||
const value = getIn(data, parseJSONPointer(from))
|
||||
|
||||
return add(data, path, value, options)
|
||||
// create new id for the copied item
|
||||
let updatedValue = cloneWithSymbols(value)
|
||||
updatedValue[META] = setIn(updatedValue[META], ['id'], createId())
|
||||
|
||||
return add(data, path, updatedValue, options)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,22 +304,19 @@ export function copy (data: ESON, path: string, from: string, options) {
|
|||
* @return {{data: ESON, revert: ESONPatch}}
|
||||
* @private
|
||||
*/
|
||||
export function move (data: ESON, path: string, from: string, options) {
|
||||
export function move (data, path, from, options) {
|
||||
const fromArray = parseJSONPointer(from)
|
||||
const prop = getIn(data, initial(toEsonPath(data, fromArray)))
|
||||
const dataValue = prop.value
|
||||
const id = prop.id // we want to use the existing id in case the move is a renaming a property
|
||||
// FIXME: only reuse the existing id when move is renaming a property in the same object
|
||||
const dataValue = getIn(data, fromArray)
|
||||
|
||||
const parentPathFrom = initial(fromArray)
|
||||
const parent = getInEson(data, parentPathFrom)
|
||||
const parent = getIn(data, parentPathFrom)
|
||||
|
||||
const result1 = remove(data, from)
|
||||
const result2 = add(result1.data, path, dataValue, options, id)
|
||||
const result2 = add(result1.data, path, dataValue, options)
|
||||
// FIXME: passing id as parameter is ugly, make that redundant (use replace instead of remove/add? (that would give less predictive output :( ))
|
||||
|
||||
const before = result1.revert[0].jsoneditor.before
|
||||
const beforeNeeded = (parent.type === 'Object' && before)
|
||||
const beforeNeeded = (parent[META].type === 'Object' && before)
|
||||
|
||||
if (result2.revert[0].op === 'replace') {
|
||||
const value = result2.revert[0].value
|
||||
|
@ -314,7 +335,7 @@ export function move (data: ESON, path: string, from: string, options) {
|
|||
return {
|
||||
data: result2.data,
|
||||
revert: beforeNeeded
|
||||
? [{ op: 'move', from: path, path: from, jsoneditor: {before} }]
|
||||
? [{ op: 'move', from: path, path: from, jsoneditor: { before } }]
|
||||
: [{ op: 'move', from: path, path: from }]
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +349,7 @@ export function move (data: ESON, path: string, from: string, options) {
|
|||
* @param {*} value
|
||||
* @return {null | Error} Returns an error when the tests, returns null otherwise
|
||||
*/
|
||||
export function test (data: ESON, path: string, value: any) {
|
||||
export function test (data, path, value) {
|
||||
if (value === undefined) {
|
||||
return new Error('Test failed, no value provided')
|
||||
}
|
||||
|
@ -338,7 +359,7 @@ export function test (data: ESON, path: string, value: any) {
|
|||
return new Error('Test failed, path not found')
|
||||
}
|
||||
|
||||
const actualValue = getInEson(data, pathArray)
|
||||
const actualValue = getIn(data, pathArray)
|
||||
if (!isEqual(esonToJson(actualValue), value)) {
|
||||
return new Error('Test failed, value differs')
|
||||
}
|
||||
|
|
|
@ -125,6 +125,7 @@ export function updateIn (object, path, callback) {
|
|||
|
||||
const key = path[0]
|
||||
const updatedValue = updateIn(object[key], path.slice(1), callback)
|
||||
// TODO: create a function applyProp(...) which does the following if/else construct
|
||||
if (object[key] === updatedValue) {
|
||||
// return original object unchanged when the new value is identical to the old one
|
||||
return object
|
||||
|
@ -206,8 +207,7 @@ export function insertAt (object, path, value) {
|
|||
throw new TypeError('Array expected at path ' + JSON.stringify(parentPath))
|
||||
}
|
||||
|
||||
const updatedItems = items.slice(0)
|
||||
|
||||
const updatedItems = cloneWithSymbols(items)
|
||||
updatedItems.splice(index, 0, value)
|
||||
|
||||
return updatedItems
|
||||
|
|
|
@ -12,35 +12,13 @@ import {
|
|||
SELECTED, SELECTED_END
|
||||
} from '../src/eson'
|
||||
import 'console.table'
|
||||
import lodashTransform from 'lodash/transform'
|
||||
import repeat from 'lodash/repeat'
|
||||
import { assertDeepEqualEson } from './utils/assertDeepEqualEson'
|
||||
|
||||
const JSON1 = loadJSON('./resources/json1.json')
|
||||
const ESON1 = loadJSON('./resources/eson1.json')
|
||||
const ESON2 = loadJSON('./resources/eson2.json')
|
||||
|
||||
test('toEsonPath', t => {
|
||||
const jsonPath = ['obj', 'arr', '2', 'last']
|
||||
const esonPath = [
|
||||
'props', '0', 'value',
|
||||
'props', '0', 'value',
|
||||
'items', '2', 'value',
|
||||
'props', '1', 'value'
|
||||
]
|
||||
t.deepEqual(toEsonPath(ESON1, jsonPath), esonPath)
|
||||
})
|
||||
|
||||
test('toJsonPath', t => {
|
||||
const jsonPath = ['obj', 'arr', '2', 'last']
|
||||
const esonPath = [
|
||||
'props', '0', 'value',
|
||||
'props', '0', 'value',
|
||||
'items', '2', 'value',
|
||||
'props', '1', 'value'
|
||||
]
|
||||
t.deepEqual(toJsonPath(ESON1, esonPath), jsonPath)
|
||||
})
|
||||
|
||||
test('jsonToEson', t => {
|
||||
assertDeepEqualEson(t, jsonToEson(1), {[META]: {id: '[ID]', path: [], type: 'value', value: 1}})
|
||||
assertDeepEqualEson(t, jsonToEson("foo"), {[META]: {id: '[ID]', path: [], type: 'value', value: "foo"}})
|
||||
|
@ -54,15 +32,24 @@ test('jsonToEson', t => {
|
|||
|
||||
const actual = jsonToEson([1,2])
|
||||
const expected = [
|
||||
{[META]: {id: '[ID]', path: [0], type: 'value', value: 1}},
|
||||
{[META]: {id: '[ID]', path: [1], type: 'value', value: 2}}
|
||||
{[META]: {id: '[ID]', path: ['0'], type: 'value', value: 1}},
|
||||
{[META]: {id: '[ID]', path: ['1'], type: 'value', value: 2}}
|
||||
]
|
||||
expected[META] = {id: '[ID]', path: [], type: 'Array'}
|
||||
assertDeepEqualEson(t, actual, expected)
|
||||
})
|
||||
|
||||
test('esonToJson', t => {
|
||||
t.deepEqual(esonToJson(ESON1), JSON1)
|
||||
const json = {
|
||||
"obj": {
|
||||
"arr": [1,2, {"first":3,"last":4}]
|
||||
},
|
||||
"str": "hello world",
|
||||
"nill": null,
|
||||
"bool": false
|
||||
}
|
||||
const eson = jsonToEson(json)
|
||||
t.deepEqual(esonToJson(eson), json)
|
||||
})
|
||||
|
||||
test('expand a single path', t => {
|
||||
|
@ -186,10 +173,19 @@ test('transform (change based on path)', t => {
|
|||
})
|
||||
|
||||
test('pathExists', t => {
|
||||
t.is(pathExists(ESON1, ['obj', 'arr', 2, 'first']), true)
|
||||
t.is(pathExists(ESON1, ['obj', 'foo']), false)
|
||||
t.is(pathExists(ESON1, ['obj', 'foo', 'bar']), false)
|
||||
t.is(pathExists(ESON1, []), true)
|
||||
const eson = jsonToEson({
|
||||
"obj": {
|
||||
"arr": [1,2, {"first":3,"last":4}]
|
||||
},
|
||||
"str": "hello world",
|
||||
"nill": null,
|
||||
"bool": false
|
||||
})
|
||||
|
||||
t.is(pathExists(eson, ['obj', 'arr', 2, 'first']), true)
|
||||
t.is(pathExists(eson, ['obj', 'foo']), false)
|
||||
t.is(pathExists(eson, ['obj', 'foo', 'bar']), false)
|
||||
t.is(pathExists(eson, []), true)
|
||||
})
|
||||
|
||||
test('parseJSONPointer', t => {
|
||||
|
@ -282,14 +278,14 @@ test('search', t => {
|
|||
const active = searchResult.active
|
||||
|
||||
t.deepEqual(matches, [
|
||||
{path: ['obj', 'arr', 2, 'last'], area: 'property'},
|
||||
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
|
||||
{path: ['str'], area: 'value'},
|
||||
{path: ['nill'], area: 'property'},
|
||||
{path: ['nill'], area: 'value'},
|
||||
{path: ['bool'], area: 'property'},
|
||||
{path: ['bool'], area: 'value'}
|
||||
])
|
||||
t.deepEqual(active, {path: ['obj', 'arr', 2, 'last'], area: 'property'})
|
||||
t.deepEqual(active, {path: ['obj', 'arr', '2', 'last'], area: 'property'})
|
||||
|
||||
let expected = esonWithSearch
|
||||
expected = setIn(expected, ['obj', 'arr', '2', 'last', META, 'searchProperty'], 'active')
|
||||
|
@ -315,31 +311,31 @@ test('nextSearchResult', t => {
|
|||
|
||||
t.deepEqual(searchResult.matches, [
|
||||
{path: ['obj', 'arr'], area: 'property'},
|
||||
{path: ['obj', 'arr', 2, 'last'], area: 'property'},
|
||||
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
|
||||
{path: ['bool'], area: 'value'}
|
||||
])
|
||||
|
||||
t.deepEqual(searchResult.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const second = nextSearchResult(searchResult.eson, searchResult.matches, searchResult.active)
|
||||
t.deepEqual(second.active, {path: ['obj', 'arr', 2, 'last'], area: 'property'})
|
||||
t.deepEqual(second.active, {path: ['obj', 'arr', '2', 'last'], area: 'property'})
|
||||
t.is(getIn(second.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const third = nextSearchResult(second.eson, second.matches, second.active)
|
||||
t.deepEqual(third.active, {path: ['bool'], area: 'value'})
|
||||
t.is(getIn(third.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['bool', META, 'searchValue']), 'active')
|
||||
|
||||
const wrappedAround = nextSearchResult(third.eson, third.matches, third.active)
|
||||
t.deepEqual(wrappedAround.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(wrappedAround.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(wrappedAround.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(wrappedAround.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(wrappedAround.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
})
|
||||
|
||||
|
@ -356,31 +352,31 @@ test('previousSearchResult', t => {
|
|||
|
||||
t.deepEqual(searchResult.matches, [
|
||||
{path: ['obj', 'arr'], area: 'property'},
|
||||
{path: ['obj', 'arr', 2, 'last'], area: 'property'},
|
||||
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
|
||||
{path: ['bool'], area: 'value'}
|
||||
])
|
||||
|
||||
t.deepEqual(searchResult.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const third = previousSearchResult(searchResult.eson, searchResult.matches, searchResult.active)
|
||||
t.deepEqual(third.active, {path: ['bool'], area: 'value'})
|
||||
t.is(getIn(third.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['bool', META, 'searchValue']), 'active')
|
||||
|
||||
const second = previousSearchResult(third.eson, third.matches, third.active)
|
||||
t.deepEqual(second.active, {path: ['obj', 'arr', 2, 'last'], area: 'property'})
|
||||
t.deepEqual(second.active, {path: ['obj', 'arr', '2', 'last'], area: 'property'})
|
||||
t.is(getIn(second.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const first = previousSearchResult(second.eson, second.matches, second.active)
|
||||
t.deepEqual(first.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(first.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(first.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(first.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(first.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
})
|
||||
|
||||
|
@ -531,36 +527,6 @@ test('pathsFromSelection (after)', t => {
|
|||
t.deepEqual(pathsFromSelection(ESON1, selection), [])
|
||||
})
|
||||
|
||||
function assertDeepEqualEson (t, actual, expected, path = [], ignoreIds = true) {
|
||||
const actualMeta = ignoreIds ? normalizeMetaIds(actual[META]) : actual[META]
|
||||
const expectedMeta = ignoreIds ? normalizeMetaIds(expected[META]) : expected[META]
|
||||
|
||||
t.deepEqual(actualMeta, expectedMeta, `Meta data not equal, path=[${path.join(', ')}]`)
|
||||
|
||||
if (actualMeta.type === 'Array') {
|
||||
t.deepEqual(actual.length, expected.length, 'Actual lengths of arrays should be equal, path=[${path.join(\', \')}]')
|
||||
actual.forEach((item, index) => assertDeepEqualEson(t, actual[index], expected[index], path.concat(index)), ignoreIds)
|
||||
}
|
||||
else if (actualMeta.type === 'Object') {
|
||||
t.deepEqual(Object.keys(actual).sort(), Object.keys(expected).sort(), 'Actual properties should be equal, path=[${path.join(\', \')}]')
|
||||
actualMeta.keys.forEach(key => assertDeepEqualEson(t, actual[key], expected[key], path.concat(key)), ignoreIds)
|
||||
}
|
||||
else { // actual[META].type === 'value'
|
||||
t.deepEqual(Object.keys(actual), [], 'Value should not contain additional properties, path=[${path.join(\', \')}]')
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeMetaIds (meta) {
|
||||
return lodashTransform(meta, (result, value, key) => {
|
||||
if (key === 'id') {
|
||||
result[key] = '[ID]'
|
||||
}
|
||||
else {
|
||||
result[key] = value
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
// helper function to print JSON in the console
|
||||
function printJSON (json, message = null) {
|
||||
if (message) {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { readFileSync } from 'fs'
|
||||
import test from 'ava'
|
||||
import { jsonToEsonOld, esonToJson, toEsonPath } from '../src/eson'
|
||||
import { patchEson, cut } from '../src/patchEson'
|
||||
|
||||
const ESON1 = loadJSON('./resources/eson1.json')
|
||||
import { META, jsonToEson, esonToJson } from '../src/eson'
|
||||
import { patchEson } from '../src/patchEson'
|
||||
import { assertDeepEqualEson } from './utils/assertDeepEqualEson'
|
||||
|
||||
test('jsonpatch add', t => {
|
||||
const json = {
|
||||
|
@ -15,21 +14,44 @@ test('jsonpatch add', t => {
|
|||
{op: 'add', path: '/obj/b', value: {foo: 'bar'}}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
const patchedJson = esonToJson(patchedData)
|
||||
|
||||
t.deepEqual(patchedJson, {
|
||||
assertDeepEqualEson(t, patchedData, jsonToEson({
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2, b: {foo: 'bar'}}
|
||||
})
|
||||
}))
|
||||
t.deepEqual(revert, [
|
||||
{op: 'remove', path: '/obj/b'}
|
||||
])
|
||||
})
|
||||
|
||||
test('jsonpatch add: insert in matrix', t => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 2}
|
||||
}
|
||||
|
||||
const patch = [
|
||||
{op: 'add', path: '/arr/1', value: 4}
|
||||
]
|
||||
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
||||
assertDeepEqualEson(t, patchedData, jsonToEson({
|
||||
arr: [1,4,2,3],
|
||||
obj: {a : 2}
|
||||
}))
|
||||
t.deepEqual(revert, [
|
||||
{op: 'remove', path: '/arr/1'}
|
||||
])
|
||||
})
|
||||
|
||||
test('jsonpatch add: append to matrix', t => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
|
@ -40,22 +62,20 @@ test('jsonpatch add: append to matrix', t => {
|
|||
{op: 'add', path: '/arr/-', value: 4}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
const patchedJson = esonToJson(patchedData)
|
||||
|
||||
t.deepEqual(patchedJson, {
|
||||
assertDeepEqualEson(t, patchedData, jsonToEson({
|
||||
arr: [1,2,3,4],
|
||||
obj: {a : 2}
|
||||
})
|
||||
}))
|
||||
t.deepEqual(revert, [
|
||||
{op: 'remove', path: '/arr/3'}
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
test('jsonpatch remove', t => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
|
@ -67,23 +87,23 @@ test('jsonpatch remove', t => {
|
|||
{op: 'remove', path: '/arr/1'},
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
const patchedJson = esonToJson(patchedData)
|
||||
|
||||
t.deepEqual(patchedJson, {
|
||||
assertDeepEqualEson(t, patchedData, jsonToEson({
|
||||
arr: [1,3],
|
||||
obj: {}
|
||||
})
|
||||
}))
|
||||
t.deepEqual(revert, [
|
||||
{op: 'add', path: '/arr/1', value: 2, jsoneditor: {type: 'value'}},
|
||||
{op: 'add', path: '/obj/a', value: 4, jsoneditor: {type: 'value', before: null}}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToEsonOld(patchedJson)
|
||||
const data2 = jsonToEson(patchedJson)
|
||||
const result2 = patchEson(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
|
@ -104,23 +124,23 @@ test('jsonpatch replace', t => {
|
|||
{op: 'replace', path: '/arr/1', value: 200},
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
const patchedJson = esonToJson(patchedData)
|
||||
|
||||
t.deepEqual(patchedJson, {
|
||||
assertDeepEqualEson(t, patchedData, jsonToEson({
|
||||
arr: [1,200,3],
|
||||
obj: {a: 400}
|
||||
})
|
||||
}))
|
||||
t.deepEqual(revert, [
|
||||
{op: 'replace', path: '/arr/1', value: 2, jsoneditor: {type: 'value'}},
|
||||
{op: 'replace', path: '/obj/a', value: 4, jsoneditor: {type: 'value'}}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToEsonOld(patchedJson)
|
||||
const data2 = jsonToEson(patchedJson)
|
||||
const result2 = patchEson(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
|
@ -133,20 +153,21 @@ test('jsonpatch replace', t => {
|
|||
])
|
||||
})
|
||||
|
||||
test('jsonpatch replace (keep ids intact)', t => {
|
||||
const json = { value: 42 }
|
||||
const patch = [
|
||||
{op: 'replace', path: '/value', value: 100}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const valueId = data.props[0].id
|
||||
|
||||
const patchedData = patchEson(data, patch).data
|
||||
const patchedValueId = patchedData.props[0].id
|
||||
|
||||
t.is(patchedValueId, valueId)
|
||||
})
|
||||
// // FIXME: keep ids intact
|
||||
// test('jsonpatch replace (keep ids intact)', t => {
|
||||
// const json = { value: 42 }
|
||||
// const patch = [
|
||||
// {op: 'replace', path: '/value', value: 100}
|
||||
// ]
|
||||
//
|
||||
// const data = jsonToEson(json)
|
||||
// const valueId = data.value[META].id
|
||||
//
|
||||
// const patchedData = patchEson(data, patch).data
|
||||
// const patchedValueId = patchedData.value[META].id
|
||||
//
|
||||
// t.is(patchedValueId, valueId)
|
||||
// })
|
||||
|
||||
test('jsonpatch copy', t => {
|
||||
const json = {
|
||||
|
@ -158,7 +179,7 @@ test('jsonpatch copy', t => {
|
|||
{op: 'copy', from: '/obj', path: '/arr/2'},
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
@ -173,7 +194,7 @@ test('jsonpatch copy', t => {
|
|||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToEsonOld(patchedJson)
|
||||
const data2 = jsonToEson(patchedJson)
|
||||
const result2 = patchEson(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
|
@ -191,18 +212,15 @@ test('jsonpatch copy (keeps the same ids)', t => {
|
|||
{op: 'copy', from: '/foo', path: '/copied'}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const fooId = data.props[0].id
|
||||
const barId = data.props[0].value.props[0].id
|
||||
const data = jsonToEson(json)
|
||||
const fooId = data.foo[META].id
|
||||
const barId = data.foo.bar[META].id
|
||||
|
||||
const patchedData = patchEson(data, patch).data
|
||||
const patchedFooId = patchedData.props[0].id
|
||||
const patchedBarId = patchedData.props[0].value.props[0].id
|
||||
const copiedId = patchedData.props[1].id
|
||||
const patchedCopiedBarId = patchedData.props[1].value.props[0].id
|
||||
|
||||
t.is(patchedData.props[0].name, 'foo')
|
||||
t.is(patchedData.props[1].name, 'copied')
|
||||
const patchedFooId = patchedData.foo[META].id
|
||||
const patchedBarId = patchedData.foo.bar[META].id
|
||||
const copiedId = patchedData.copied[META].id
|
||||
const patchedCopiedBarId = patchedData.copied.bar[META].id
|
||||
|
||||
t.is(patchedFooId, fooId, 'same foo id')
|
||||
t.is(patchedBarId, barId, 'same bar id')
|
||||
|
@ -210,7 +228,6 @@ test('jsonpatch copy (keeps the same ids)', t => {
|
|||
t.not(copiedId, fooId, 'different id of property copied')
|
||||
|
||||
// The id's of the copied childs are the same, that's okish, they will not bite each other
|
||||
// FIXME: better solution for id's either always unique, or unique per object/array
|
||||
t.is(patchedCopiedBarId, patchedBarId, 'same copied bar id')
|
||||
})
|
||||
|
||||
|
@ -224,7 +241,7 @@ test('jsonpatch move', t => {
|
|||
{op: 'move', from: '/obj', path: '/arr/2'},
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
@ -239,7 +256,7 @@ test('jsonpatch move', t => {
|
|||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToEsonOld(patchedJson)
|
||||
const data2 = jsonToEson(patchedJson)
|
||||
const result2 = patchEson(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
|
@ -260,7 +277,7 @@ test('jsonpatch move before', t => {
|
|||
{op: 'move', from: '/obj', path: '/arr/2'},
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
@ -276,7 +293,7 @@ test('jsonpatch move before', t => {
|
|||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToEsonOld(patchedJson)
|
||||
const data2 = jsonToEson(patchedJson)
|
||||
const result2 = patchEson(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
|
@ -293,7 +310,7 @@ test('jsonpatch move and replace', t => {
|
|||
{op: 'move', from: '/a', path: '/b'},
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
|
@ -301,24 +318,9 @@ test('jsonpatch move and replace', t => {
|
|||
const patchedJson = esonToJson(patchedData)
|
||||
|
||||
// id of the replaced B must be kept intact
|
||||
t.is(patchedData.props[0].id, data.props[1].id)
|
||||
|
||||
replaceIds(patchedData)
|
||||
t.deepEqual(patchedData, {
|
||||
"type": "Object",
|
||||
"expanded": true,
|
||||
"props": [
|
||||
{
|
||||
"id": "[ID]",
|
||||
"name": "b",
|
||||
"value": {
|
||||
"type": "value",
|
||||
"value": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
t.is(patchedData.b[META].id, data.b[META].id)
|
||||
|
||||
assertDeepEqualEson(t, patchedData, jsonToEson({b: 2}))
|
||||
t.deepEqual(patchedJson, { b : 2 })
|
||||
t.deepEqual(revert, [
|
||||
{op:'move', from: '/b', path: '/a'},
|
||||
|
@ -326,7 +328,7 @@ test('jsonpatch move and replace', t => {
|
|||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToEsonOld(patchedJson)
|
||||
const data2 = jsonToEson(patchedJson)
|
||||
const result2 = patchEson(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
|
@ -349,7 +351,7 @@ test('jsonpatch move and replace (nested)', t => {
|
|||
{op: 'move', from: '/obj', path: '/arr'},
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
@ -364,7 +366,7 @@ test('jsonpatch move and replace (nested)', t => {
|
|||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToEsonOld(patchedJson)
|
||||
const data2 = jsonToEson(patchedJson)
|
||||
const result2 = patchEson(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
|
@ -383,32 +385,31 @@ test('jsonpatch move (keep id intact)', t => {
|
|||
{op: 'move', from: '/value', path: '/moved'}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const valueId = data.props[0].id
|
||||
const data = jsonToEson(json)
|
||||
const valueId = data.value[META].id
|
||||
|
||||
const patchedData = patchEson(data, patch).data
|
||||
const patchedValueId = patchedData.props[0].id
|
||||
const patchedValueId = patchedData.moved[META].id
|
||||
|
||||
t.is(patchedValueId, valueId)
|
||||
})
|
||||
|
||||
test('jsonpatch move and replace (keep ids intact)', t => {
|
||||
const json = { a: 2, b: 3 }
|
||||
const patch = [
|
||||
{op: 'move', from: '/a', path: '/b'}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const bId = data.props[1].id
|
||||
|
||||
t.is(data.props[0].name, 'a')
|
||||
t.is(data.props[1].name, 'b')
|
||||
|
||||
const patchedData = patchEson(data, patch).data
|
||||
|
||||
t.is(patchedData.props[0].name, 'b')
|
||||
t.is(patchedData.props[0].id, bId)
|
||||
})
|
||||
// test('jsonpatch move and replace (keep ids intact)', t => {
|
||||
// const json = { a: 2, b: 3 }
|
||||
// const patch = [
|
||||
// {op: 'move', from: '/a', path: '/b'}
|
||||
// ]
|
||||
//
|
||||
// const data = jsonToEson(json)
|
||||
// const bId = data.b[META].id
|
||||
//
|
||||
// t.deepEqual(data[META].keys, ['a', 'b'])
|
||||
//
|
||||
// const patchedData = patchEson(data, patch).data
|
||||
//
|
||||
// t.is(patchedData.b[META].id, bId)
|
||||
// t.deepEqual(data[META].keys, ['b'])
|
||||
// })
|
||||
|
||||
test('jsonpatch test (ok)', t => {
|
||||
const json = {
|
||||
|
@ -421,7 +422,7 @@ test('jsonpatch test (ok)', t => {
|
|||
{op: 'add', path: '/added', value: 'ok'}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
@ -449,7 +450,7 @@ test('jsonpatch test (fail: path not found)', t => {
|
|||
{op: 'add', path: '/added', value: 'ok'}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
@ -475,7 +476,7 @@ test('jsonpatch test (fail: value not equal)', t => {
|
|||
{op: 'add', path: '/added', value: 'ok'}
|
||||
]
|
||||
|
||||
const data = jsonToEsonOld(json)
|
||||
const data = jsonToEson(json)
|
||||
const result = patchEson(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
|
@ -490,23 +491,6 @@ test('jsonpatch test (fail: value not equal)', t => {
|
|||
t.is(result.error.toString(), 'Error: Test failed, value differs')
|
||||
})
|
||||
|
||||
// helper function to replace all id properties with a constant value
|
||||
function replaceIds (data, value = '[ID]') {
|
||||
if (data.type === 'Object') {
|
||||
data.props.forEach(prop => {
|
||||
prop.id = value
|
||||
replaceIds(prop.value, value)
|
||||
})
|
||||
}
|
||||
|
||||
if (data.type === 'Array') {
|
||||
data.items.forEach(item => {
|
||||
item.id = value
|
||||
replaceIds(item.value, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to print JSON in the console
|
||||
function printJSON (json, message = null) {
|
||||
if (message) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// TODO: move assertDeepEqualEson into a separate function
|
||||
import {META} from "../../src/eson"
|
||||
import lodashTransform from "lodash/transform"
|
||||
|
||||
export function assertDeepEqualEson (t, actual, expected, path = [], ignoreIds = true) {
|
||||
if (expected === undefined) {
|
||||
throw new Error('Argument "expected" is undefined')
|
||||
}
|
||||
|
||||
// console.log('assertDeepEqualEson', actual, expected)
|
||||
|
||||
const actualMeta = ignoreIds ? normalizeMetaIds(actual[META]) : actual[META]
|
||||
const expectedMeta = ignoreIds ? normalizeMetaIds(expected[META]) : expected[META]
|
||||
|
||||
t.deepEqual(actualMeta, expectedMeta, `Meta data not equal, path=[${path.join(', ')}], actual[META]=${JSON.stringify(actualMeta)}, expected[META]=${JSON.stringify(expectedMeta)}`)
|
||||
|
||||
if (actualMeta.type === 'Array') {
|
||||
t.deepEqual(actual.length, expected.length, 'Actual lengths of arrays should be equal, path=[${path.join(\', \')}]')
|
||||
actual.forEach((item, index) => assertDeepEqualEson(t, actual[index], expected[index], path.concat(index)), ignoreIds)
|
||||
}
|
||||
else if (actualMeta.type === 'Object') {
|
||||
t.deepEqual(Object.keys(actual).sort(), Object.keys(expected).sort(), 'Actual properties should be equal, path=[${path.join(\', \')}]')
|
||||
actualMeta.keys.forEach(key => assertDeepEqualEson(t, actual[key], expected[key], path.concat(key)), ignoreIds)
|
||||
}
|
||||
else { // actual[META].type === 'value'
|
||||
t.deepEqual(Object.keys(actual), [], 'Value should not contain additional properties, path=[${path.join(\', \')}]')
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeMetaIds (meta) {
|
||||
return lodashTransform(meta, (result, value, key) => {
|
||||
if (key === 'id') {
|
||||
result[key] = '[ID]'
|
||||
}
|
||||
else {
|
||||
result[key] = value
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
Loading…
Reference in New Issue