Changed data model to items and props having an id

This commit is contained in:
jos 2017-01-06 13:57:16 +01:00
parent 0f7c601635
commit ab8d4b4be6
5 changed files with 542 additions and 255 deletions

View File

@ -53,10 +53,10 @@ export default class JSONNode extends Component {
if (data.expanded) {
if (data.props.length > 0) {
const props = data.props.map(prop => {
return h('li', {key : prop.name},
return h('li', {key: prop.id},
h(this.constructor, {
parent: this,
prop: prop,
prop,
data: prop.value,
options,
events
@ -78,6 +78,7 @@ export default class JSONNode extends Component {
return h('div', {}, [node, childs])
}
// TODO: extract a function renderChilds shared by both renderJSONObject and renderJSONArray (rename .props and .items to .childs?)
renderJSONArray ({prop, index, data, options, events}) {
const childCount = data.items.length
const node = h('div', {name: compileJSONPointer(this.getPath()), key: 'node', className: 'jsoneditor-node jsoneditor-array'}, [
@ -91,12 +92,12 @@ export default class JSONNode extends Component {
let childs
if (data.expanded) {
if (data.items.length > 0) {
const items = data.items.map((child, index) => {
return h('li', {key : index},
const items = data.items.map((item, index) => {
return h('li', {key : item.id},
h(this.constructor, {
parent: this,
index,
data: child,
data: item.value,
options,
events
})
@ -287,8 +288,6 @@ export default class JSONNode extends Component {
target = target.parentNode
}
console.log('value', this.props)
target.className = JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
JSONNode.getSearchResultClass(this.props.data.searchResult)
target.title = itsAnUrl ? JSONNode.URL_TITLE : ''

View File

@ -118,16 +118,11 @@ export default class TreeMode extends Component {
}
// enrich the data with search results
// TODO: performance improvements in search would be nice though it's acceptable right now
const searchResults = this.state.search.text ? search(data, this.state.search.text) : null
if (searchResults) {
data = addSearchResults(data, searchResults, this.state.search.active)
}
// TODO: moveTo active search result (not focus!)
// if (this.state.search.active) {
// data = addFocus(data, this.state.search.active)
// }
// console.log('data', data)
return h('div', {
className: `jsoneditor jsoneditor-mode-${props.mode}`,

View File

@ -9,7 +9,10 @@ import { setIn, updateIn, getIn, deleteIn, insertAt } from './utils/immutability
import { isObject } from './utils/typeUtils'
import isEqual from 'lodash/isEqual'
import type {JSONData, DataPointer, Path} from './types'
import type {
JSONData, ObjectData, ItemData, DataPointer, Path,
JSONPatch, JSONPatchAction, PatchOptions, JSONPatchResult
} from './types'
/**
* Expand function which will expand all nodes
@ -33,15 +36,21 @@ export function jsonToData (json, expand = expandAll, path = [], type = 'value')
return {
type: 'Array',
expanded: expand(path),
items: json.map((child, index) => jsonToData(child, expand, path.concat(index)))
items: json.map((child, index) => {
return {
id: getId(), // TODO: use id based on index (only has to be unique within this array)
value: jsonToData(child, expand, path.concat(index))
}
})
}
}
else if (isObject(json)) {
return {
type: 'Object',
expanded: expand(path),
props: Object.keys(json).map(name => {
props: Object.keys(json).map((name, index) => {
return {
id: getId(), // TODO: use id based on index (only has to be unique within this array)
name,
value: jsonToData(json[name], expand, path.concat(name))
}
@ -61,10 +70,10 @@ export function jsonToData (json, expand = expandAll, path = [], type = 'value')
* @param {JSONData} data
* @return {Object | Array | string | number | boolean | null} json
*/
export function dataToJson (data) {
export function dataToJson (data: JSONData) {
switch (data.type) {
case 'Array':
return data.items.map(dataToJson)
return data.items.map(item => dataToJson(item.value))
case 'Object':
const object = {}
@ -87,7 +96,7 @@ export function dataToJson (data) {
* @return {Path} dataPath
* @private
*/
export function toDataPath (data, path) {
export function toDataPath (data: JSONData, path: Path) : Path {
if (path.length === 0) {
return []
}
@ -95,14 +104,14 @@ export function toDataPath (data, path) {
if (data.type === 'Array') {
// index of an array
const index = path[0]
const item = data.items[index]
const item = data.items[parseInt(index)]
if (!item) {
throw new Error('Array item "' + index + '" not found')
}
return ['items', index].concat(toDataPath(item, path.slice(1)))
return ['items', index, 'value'].concat(toDataPath(item.value, path.slice(1)))
}
else {
else if (data.type === 'Object') {
// object property. find the index of this property
const index = findPropertyIndex(data, path[0])
const prop = data.props[index]
@ -111,19 +120,11 @@ export function toDataPath (data, path) {
}
return ['props', index, 'value']
.concat(toDataPath(prop && prop.value, path.slice(1)))
.concat(toDataPath(prop.value, path.slice(1)))
}
else {
return []
}
}
/**
* Convert a path of a JSON object into a path in the corresponding data model
* @param {JSONData} data
* @param {Path} path
* @return {Path} dataPath
* @private
*/
export function toPath (data, dataPath) {
}
/**
@ -134,7 +135,7 @@ export function toPath (data, dataPath) {
* what nodes must be expanded
* @return {{data: JSONData, revert: Object[], error: Error | null}}
*/
export function patchData (data, patch, expand = expandAll) {
export function patchData (data: JSONData, patch: JSONPatchAction[], expand = expandAll) {
let updatedData = data
let revert = []
@ -142,6 +143,8 @@ export function patchData (data, patch, expand = expandAll) {
const action = patch[i]
const options = action.jsoneditor
// TODO: check whether action.op and action.path exist
switch (action.op) {
case 'add': {
const path = parseJSONPointer(action.path)
@ -172,6 +175,14 @@ export function patchData (data, patch, expand = expandAll) {
}
case 'copy': {
if (!action.from) {
return {
data,
revert: [],
error: new Error('Property "from" expected in copy action ' + JSON.stringify(action))
}
}
const result = copy(updatedData, action.path, action.from, options)
updatedData = result.data
revert = result.revert.concat(revert)
@ -180,6 +191,14 @@ export function patchData (data, patch, expand = expandAll) {
}
case 'move': {
if (!action.from) {
return {
data,
revert: [],
error: new Error('Property "from" expected in move action ' + JSON.stringify(action))
}
}
const result = move(updatedData, action.path, action.from, options)
updatedData = result.data
revert = result.revert.concat(revert)
@ -224,7 +243,7 @@ export function patchData (data, patch, expand = expandAll) {
* @param {JSONData} value
* @return {{data: JSONData, revert: JSONPatch}}
*/
export function replace (data, path, value) {
export function replace (data: JSONData, path: Path, value: JSONData) {
const dataPath = toDataPath(data, path)
const oldValue = getIn(data, dataPath)
@ -247,7 +266,7 @@ export function replace (data, path, value) {
* @param {string} path
* @return {{data: JSONData, revert: JSONPatch}}
*/
export function remove (data, path) {
export function remove (data: JSONData, path: string) {
// console.log('remove', path)
const pathArray = parseJSONPointer(path)
@ -259,6 +278,9 @@ export function remove (data, path) {
if (parent.type === 'Array') {
const dataPath = toDataPath(data, pathArray)
// remove the 'value' property, we want to remove the whole item from the items array
dataPath.pop()
return {
data: deleteIn(data, dataPath),
revert: [{
@ -275,7 +297,9 @@ export function remove (data, path) {
const dataPath = toDataPath(data, pathArray)
const prop = pathArray[pathArray.length - 1]
dataPath.pop() // remove the 'value' property, we want to remove the whole object property
// remove the 'value' property, we want to remove the whole object property from props
dataPath.pop()
return {
data: deleteIn(data, dataPath),
revert: [{
@ -298,12 +322,12 @@ export function remove (data, path) {
* @param {JSONPatch} patch
* @return {Array}
*/
export function simplifyPatch(patch) {
export function simplifyPatch(patch: JSONPatch) {
const simplifiedPatch = []
const paths = {}
// loop over the patch from last to first
for (var i = patch.length - 1; i >= 0; i--) {
for (let i = patch.length - 1; i >= 0; i--) {
const action = patch[i]
if (action.op === 'test') {
// ignore test actions
@ -331,7 +355,7 @@ export function simplifyPatch(patch) {
* @return {{data: JSONData, revert: JSONPatch}}
* @private
*/
export function add (data, path, value, options) {
export function add (data: JSONData, path: string, value: JSONData, options) {
const pathArray = parseJSONPointer(path)
const parentPath = pathArray.slice(0, pathArray.length - 1)
const dataPath = toDataPath(data, parentPath)
@ -341,16 +365,29 @@ export function add (data, path, value, options) {
let updatedData
if (parent.type === 'Array') {
updatedData = insertAt(data, dataPath.concat('items', prop), value)
const newItem = {
id: getId(), // TODO: create a unique id within current id's instead of using a global, ever incrementing id
value
}
updatedData = insertAt(data, dataPath.concat('items', prop), newItem)
}
else { // parent.type === 'Object'
updatedData = updateIn(data, dataPath, (object) => {
const newProp = { name: prop, value }
const index = (options && typeof options.before === 'string')
? findPropertyIndex(object, options.before) // insert before
: object.props.length // append
const existingIndex = findPropertyIndex(object, prop)
if (existingIndex !== -1) {
// replace existing item
return setIn(object, ['props', existingIndex, 'value'], value)
}
else {
// insert new item
const newId = getId()
const newProp = { id: newId, name: prop, value }
const index = (options && typeof options.before === 'string')
? findPropertyIndex(object, options.before) // insert before
: object.props.length // append
return insertAt(object, ['props', index], newProp)
return insertAt(object, ['props', index], newProp)
}
})
}
@ -387,7 +424,7 @@ export function add (data, path, value, options) {
* @return {{data: JSONData, revert: JSONPatch}}
* @private
*/
export function copy (data, path, from, options) {
export function copy (data: JSONData, path: string, from: string, options) {
const value = getIn(data, toDataPath(data, parseJSONPointer(from)))
return add(data, path, value, options)
@ -402,7 +439,7 @@ export function copy (data, path, from, options) {
* @return {{data: JSONData, revert: JSONPatch}}
* @private
*/
export function move (data, path, from, options) {
export function move (data: JSONData, path: string, from: string, options) {
const fromArray = parseJSONPointer(from)
const dataValue = getIn(data, toDataPath(data, fromArray))
@ -446,7 +483,7 @@ export function move (data, path, from, options) {
* @param {*} value
* @return {null | Error} Returns an error when the tests, returns null otherwise
*/
export function test (data, path, value) {
export function test (data: JSONData, path: string, value: any) {
if (value === undefined) {
return new Error('Test failed, no value provided')
}
@ -462,6 +499,8 @@ export function test (data, path, value) {
}
}
type ExpandCallback = (Path) => boolean
/**
* Expand or collapse one or multiple items or properties
* @param {JSONData} data
@ -472,7 +511,7 @@ export function test (data, path, value) {
* @param {boolean} [expanded=true] New expanded state: true to expand, false to collapse
* @return {JSONData}
*/
export function expand (data, callback, expanded: boolean = true) {
export function expand (data: JSONData, callback: Path | (Path) => boolean, expanded: boolean = true) {
// console.log('expand', callback, expand)
if (typeof callback === 'function') {
@ -499,7 +538,7 @@ export function expand (data, callback, expanded: boolean = true) {
/**
* Expand all Objects and Arrays on a path
*/
export function expandPath (data: JSONData, path: Path) {
export function expandPath (data: JSONData, path: Path) : JSONData {
let updatedData = data
if (path) {
@ -521,7 +560,7 @@ export function expandPath (data: JSONData, path: Path) {
* @param {JSONData} data
* @param {JSONSchemaError[]} errors
*/
export function addErrors (data, errors) {
export function addErrors (data: JSONData, errors) {
let updatedData = data
if (errors) {
@ -641,24 +680,6 @@ export function addSearchResults (data: JSONData, searchResults: DataPointer[],
return updatedData
}
/**
* Merge a object describing where the focus is to the data
*/
export function addFocus (data: JSONData, focusOn: DataPointer) {
if (focusOn.type == 'value') {
const dataPath = toDataPath(data, focusOn.path).concat('focus')
return setIn(data, dataPath, true)
}
if (focusOn.type === 'property') {
const valueDataPath = toDataPath(data, focusOn.path)
const propertyDataPath = allButLast(valueDataPath).concat('focus')
return setIn(data, propertyDataPath, true)
}
return data
}
/**
* Do a case insensitive search for a search text in a text
* @param {String} text
@ -669,13 +690,15 @@ export function containsCaseInsensitive (text: string, search: string): boolean
return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1
}
type RecurseCallback = (JSONData, Path, JSONData) => JSONData
/**
* Recursively transform JSONData: a recursive "map" function
* @param {JSONData} data
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
* @return {JSONData} Returns the transformed data
*/
export function transform (data, callback) {
export function transform (data: JSONData, callback: RecurseCallback) {
return recurseTransform (data, [], data, callback)
}
@ -687,40 +710,33 @@ export function transform (data, callback) {
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
* @return {JSONData} Returns the transformed data
*/
function recurseTransform (value, path, root, callback) {
function recurseTransform (value: JSONData, path: Path, root?: JSONData, callback: RecurseCallback) : JSONData{
let updatedValue = callback(value, path, root)
switch (value.type) {
case 'Array': {
let updatedItems = updatedValue.items
if (value.type === 'Array') {
let updatedItems = updatedValue.items
updatedValue.items.forEach((item, index) => {
const updatedItem = recurseTransform(item, path.concat(String(index)), root, callback)
updatedItems = setIn(updatedItems, [index], updatedItem)
})
updatedValue.items.forEach((item, index) => {
const updatedItem = recurseTransform(item.value, path.concat(String(index)), root, callback)
updatedItems = setIn(updatedItems, [index, 'value'], updatedItem)
})
updatedValue = setIn(updatedValue, ['items'], updatedItems)
break
}
case 'Object': {
let updatedProps = updatedValue.props
updatedValue.props.forEach((prop, index) => {
const updatedItem = recurseTransform(prop.value, path.concat(prop.name), root, callback)
updatedProps = setIn(updatedProps, [index, 'value'], updatedItem)
})
updatedValue = setIn(updatedValue, ['props'], updatedProps)
break
}
default: // type 'string' or 'value'
// no childs to traverse
updatedValue = setIn(updatedValue, ['items'], updatedItems)
}
if (value.type === 'Object') {
let updatedProps = updatedValue.props
updatedValue.props.forEach((prop, index) => {
const updatedItem = recurseTransform(prop.value, path.concat(prop.name), root, callback)
updatedProps = setIn(updatedProps, [index, 'value'], updatedItem)
})
updatedValue = setIn(updatedValue, ['props'], updatedProps)
}
// (for type 'string' or 'value' there are no childs to traverse)
return updatedValue
}
@ -729,7 +745,7 @@ function recurseTransform (value, path, root, callback) {
* @param {JSONData} data
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
*/
export function traverse (data, callback) {
export function traverse (data: JSONData, callback: RecurseCallback) {
return recurseTraverse (data, [], data, callback)
}
@ -740,13 +756,13 @@ export function traverse (data, callback) {
* @param {JSONData | null} root The root object, object at path=[]
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
*/
function recurseTraverse (value, path, root, callback) {
function recurseTraverse (value: JSONData, path: Path, root: JSONData, callback: RecurseCallback) {
callback(value, path, root)
switch (value.type) {
case 'Array': {
value.items.forEach((item, index) => {
recurseTraverse(item, path.concat(String(index)), root, callback)
value.items.forEach((item: ItemData, index) => {
recurseTraverse(item.value, path.concat(String(index)), root, callback)
})
break
}
@ -783,7 +799,9 @@ export function pathExists (data, path) {
if (data.type === 'Array') {
// index of an array
index = path[0]
return pathExists(data.items[index], path.slice(1))
const item = data.items[index]
return pathExists(item && item.value, path.slice(1))
}
else {
// object property. find the index of this property
@ -825,7 +843,7 @@ export function resolvePathIndex (data, path) {
* @return {string | null} Returns the name of the next property,
* or null if there is none
*/
export function findNextProp (parent, prop) {
export function findNextProp (parent: ObjectData, prop: string) : string | null {
const index = findPropertyIndex(parent, prop)
if (index === -1) {
return null
@ -841,7 +859,7 @@ export function findNextProp (parent, prop) {
* @param {string} prop
* @return {number} Returns the index when found, -1 when not found
*/
export function findPropertyIndex (object, prop) {
export function findPropertyIndex (object: ObjectData, prop: string) {
return object.props.findIndex(p => p.name === prop)
}
@ -851,7 +869,7 @@ export function findPropertyIndex (object, prop) {
* @param {string} pointer
* @return {Array}
*/
export function parseJSONPointer (pointer) {
export function parseJSONPointer (pointer: string) {
const path = pointer.split('/')
path.shift() // remove the first empty entry
@ -864,12 +882,33 @@ export function parseJSONPointer (pointer) {
* @param {Path} path
* @return {string}
*/
export function compileJSONPointer (path) {
export function compileJSONPointer (path: Path) {
return path
.map(p => '/' + String(p).replace(/~/g, '~0').replace(/\//g, '~1'))
.join('')
}
/**
* Get a new "unique" id. Id's are created from an incremental counter.
* @return {number}
*/
// TODO: use createUniqueId instead of getId()
function getId () : number {
_id++
return _id
}
let _id = 0
/**
* Find a unique id from an array with properties each having an id field.
* The
* @param {{id: string}} array
*/
// TODO: use createUniqueId instead of getId()
function createUniqueId (array) {
return Math.max(...array.map(item => item.id)) + 1
}
/**
* Returns the last item of an array
*/

View File

@ -1,19 +1,6 @@
// @flow
/**
*
* @typedef {'Object' | 'Array' | 'value' | 'string'} JSONDataType
*
* @typedef {{
* patch: JSONPatch,
* revert: JSONPatch,
* error: null | Error
* }} JSONPatchResult
*
* @typedef {{
* dataPath: string,
* message: string
* }} JSONSchemaError
*
* @typedef {{
* name: string?,
@ -33,10 +20,6 @@
* ace: Object?
* }} Options
*
* @typedef {{
* expand: function (path: Path)?
* }} PatchOptions
*
*/
@ -53,11 +36,17 @@ export type SearchResultStatus = 'normal' | 'active'
export type DataPointerType = 'value' | 'property'
export type PropertyData = {
id: number,
name: string,
value: JSONData,
searchResult: ?SearchResultStatus
}
export type ItemData = {
id: number,
value: JSONData
}
export type ObjectData = {
type: 'Object',
expanded: ?boolean,
@ -67,7 +56,7 @@ export type ObjectData = {
export type ArrayData = {
type: 'Array',
expanded: ?boolean,
items: ?JSONData[]
items: ItemData[]
}
export type ValueData = {
@ -78,6 +67,8 @@ export type ValueData = {
export type JSONData = ObjectData | ArrayData | ValueData
export type JSONDataType = 'Object' | 'Array' | 'value' | 'string'
export type Path = string[]
@ -96,8 +87,25 @@ export type SetOptions = {
export type JSONPatchAction = {
op: string, // TODO: define allowed ops
path?: string,
path: string,
from?: string,
value?: any
value?: any,
jsoneditor?: PatchOptions
}
export type JSONPatch = JSONPatchAction[]
export type PatchOptions = {
type: JSONDataType,
expand: (Path) => boolean
}
export type JSONPatchResult = {
patch: JSONPatch,
revert: JSONPatch,
error: null | Error
}
export type JSONSchemaError = {
dataPath: string,
message: string
}

View File

@ -20,45 +20,58 @@ const JSON_DATA_EXAMPLE = {
expanded: true,
props: [
{
id: '[ID]',
name: 'obj',
value: {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'arr',
value: {
type: 'Array',
expanded: true,
items: [
{
type: 'value',
value: 1
id: '[ID]',
value: {
type: 'value',
value: 1
}
},
{
type: 'value',
value: 2
id: '[ID]',
value: {
type: 'value',
value: 2
}
},
{
type: 'Object',
expanded: true,
props: [
{
name: 'first',
value: {
type: 'value',
value: 3
id: '[ID]',
value: {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'first',
value: {
type: 'value',
value: 3
}
},
{
id: '[ID]',
name: 'last',
value: {
type: 'value',
value: 4
}
}
},
{
name: 'last',
value: {
type: 'value',
value: 4
}
}
]
},
]
}
}
]
}
}
@ -66,6 +79,7 @@ const JSON_DATA_EXAMPLE = {
}
},
{
id: '[ID]',
name: 'str',
value: {
type: 'value',
@ -73,6 +87,7 @@ const JSON_DATA_EXAMPLE = {
}
},
{
id: '[ID]',
name: 'nill',
value: {
type: 'value',
@ -80,6 +95,7 @@ const JSON_DATA_EXAMPLE = {
}
},
{
id: '[ID]',
name: 'bool',
value: {
type: 'value',
@ -89,50 +105,66 @@ const JSON_DATA_EXAMPLE = {
]
}
// TODO: instead of all slightly different copies of JSON_DATA_EXAMPLE, built them up via setIn, updateIn based on JSON_DATA_EXAMPLE
const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'obj',
value: {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'arr',
value: {
type: 'Array',
expanded: true,
items: [
{
type: 'value',
value: 1
id: '[ID]',
value: {
type: 'value',
value: 1
}
},
{
type: 'value',
value: 2
id: '[ID]',
value: {
type: 'value',
value: 2
}
},
{
type: 'Object',
expanded: false,
props: [
{
name: 'first',
value: {
type: 'value',
value: 3
id: '[ID]',
value: {
type: 'Object',
expanded: false,
props: [
{
id: '[ID]',
name: 'first',
value: {
type: 'value',
value: 3
}
},
{
id: '[ID]',
name: 'last',
value: {
type: 'value',
value: 4
}
}
},
{
name: 'last',
value: {
type: 'value',
value: 4
}
}
]
},
]
}
}
]
}
}
@ -140,6 +172,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
}
},
{
id: '[ID]',
name: 'str',
value: {
type: 'value',
@ -147,6 +180,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
}
},
{
id: '[ID]',
name: 'nill',
value: {
type: 'value',
@ -154,6 +188,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
}
},
{
id: '[ID]',
name: 'bool',
value: {
type: 'value',
@ -168,45 +203,58 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
expanded: true,
props: [
{
id: '[ID]',
name: 'obj',
value: {
type: 'Object',
expanded: false,
props: [
{
id: '[ID]',
name: 'arr',
value: {
type: 'Array',
expanded: false,
items: [
{
type: 'value',
value: 1
id: '[ID]',
value: {
type: 'value',
value: 1
}
},
{
type: 'value',
value: 2
id: '[ID]',
value: {
type: 'value',
value: 2
}
},
{
type: 'Object',
expanded: false,
props: [
{
name: 'first',
value: {
type: 'value',
value: 3
id: '[ID]',
value: {
type: 'Object',
expanded: false,
props: [
{
id: '[ID]',
name: 'first',
value: {
type: 'value',
value: 3
}
},
{
id: '[ID]',
name: 'last',
value: {
type: 'value',
value: 4
}
}
},
{
name: 'last',
value: {
type: 'value',
value: 4
}
}
]
},
]
}
}
]
}
}
@ -214,6 +262,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
}
},
{
id: '[ID]',
name: 'str',
value: {
type: 'value',
@ -221,6 +270,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
}
},
{
id: '[ID]',
name: 'nill',
value: {
type: 'value',
@ -228,6 +278,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
}
},
{
id: '[ID]',
name: 'bool',
value: {
type: 'value',
@ -243,45 +294,58 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
expanded: true,
props: [
{
id: '[ID]',
name: 'obj',
value: {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'arr',
value: {
type: 'Array',
expanded: true,
items: [
{
type: 'value',
value: 1
id: '[ID]',
value: {
type: 'value',
value: 1
}
},
{
type: 'value',
value: 2
id: '[ID]',
value: {
type: 'value',
value: 2
}
},
{
type: 'Object',
expanded: true,
props: [
{
name: 'first',
value: {
type: 'value',
value: 3
}
},
{
name: 'last',
value: {
type: 'value',
value: 4
id: '[ID]',
value: {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'first',
value: {
type: 'value',
value: 3
}
},
searchResult: 'active'
}
]
{
id: '[ID]',
name: 'last',
value: {
type: 'value',
value: 4
},
searchResult: 'active'
}
]
}
}
]
}
@ -290,6 +354,7 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
}
},
{
id: '[ID]',
name: 'str',
value: {
type: 'value',
@ -298,6 +363,7 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
}
},
{
id: '[ID]',
name: 'nill',
value: {
type: 'value',
@ -307,6 +373,7 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
searchResult: 'normal'
},
{
id: '[ID]',
name: 'bool',
value: {
type: 'value',
@ -322,11 +389,13 @@ const JSON_DATA_SMALL = {
type: 'Object',
props: [
{
id: '[ID]',
name: 'obj',
value: {
type: 'Object',
props: [
{
id: '[ID]',
name: 'a',
value: {
type: 'value',
@ -337,13 +406,17 @@ const JSON_DATA_SMALL = {
}
},
{
id: '[ID]',
name: 'arr',
value: {
type: 'Array',
items: [
{
type: 'value',
value: 3
id: '[ID]',
value: {
type: 'value',
value: 3
}
}
]
}
@ -362,46 +435,59 @@ const JSON_DATA_EXAMPLE_ERRORS = {
expanded: true,
props: [
{
id: '[ID]',
name: 'obj',
value: {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'arr',
value: {
type: 'Array',
expanded: true,
items: [
{
type: 'value',
value: 1
id: '[ID]',
value: {
type: 'value',
value: 1
}
},
{
type: 'value',
value: 2
id: '[ID]',
value: {
type: 'value',
value: 2
}
},
{
type: 'Object',
expanded: true,
props: [
{
name: 'first',
value: {
type: 'value',
value: 3
id: '[ID]',
value: {
type: 'Object',
expanded: true,
props: [
{
id: '[ID]',
name: 'first',
value: {
type: 'value',
value: 3
}
},
{
id: '[ID]',
name: 'last',
value: {
type: 'value',
value: 4,
error: JSON_SCHEMA_ERRORS[0]
}
}
},
{
name: 'last',
value: {
type: 'value',
value: 4,
error: JSON_SCHEMA_ERRORS[0]
}
}
]
},
]
}
}
]
}
}
@ -409,6 +495,7 @@ const JSON_DATA_EXAMPLE_ERRORS = {
}
},
{
id: '[ID]',
name: 'str',
value: {
type: 'value',
@ -416,6 +503,7 @@ const JSON_DATA_EXAMPLE_ERRORS = {
}
},
{
id: '[ID]',
name: 'nill',
value: {
type: 'value',
@ -424,6 +512,7 @@ const JSON_DATA_EXAMPLE_ERRORS = {
}
},
{
id: '[ID]',
name: 'bool',
value: {
type: 'value',
@ -438,7 +527,10 @@ test('jsonToData', t => {
return true
}
t.deepEqual(jsonToData(JSON_EXAMPLE, expand, []), JSON_DATA_EXAMPLE)
const jsonData = jsonToData(JSON_EXAMPLE, expand, [])
replaceIds(jsonData)
t.deepEqual(jsonData, JSON_DATA_EXAMPLE)
})
test('dataToJson', t => {
@ -622,6 +714,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 = jsonToData(json)
const valueId = data.props[0].id
const patchedData = patchData(data, patch).data
const patchedValueId = patchedData.props[0].id
t.is(patchedValueId, valueId)
})
test('jsonpatch copy', t => {
const json = {
arr: [1,2,3],
@ -659,6 +766,34 @@ test('jsonpatch copy', t => {
])
})
// test('jsonpatch copy (create new ids)', t => {
// const json = { foo: { bar: 42 } }
// const patch = [
// {op: 'copy', from: '/foo', path: '/copied'}
// ]
//
// const data = jsonToData(json)
// const objectId = data.id
// const fooId = data.props[0].value.id
// const barId = data.props[0].value.props[0].value.id
//
// const patchedData = patchData(data, patch).data
// const patchedObjectId = patchedData.id
// const patchedFooId = patchedData.props[0].value.id
// const patchedBarId = patchedData.props[0].value.props[0].value.id
// const patchedCopiedId = patchedData.props[1].value.id
// const patchedCopiedBarId = patchedData.props[1].value.props[0].value.id
//
// t.is(patchedData.props[0].name, 'foo')
// t.is(patchedData.props[1].name, 'copied')
//
// t.is(patchedObjectId, objectId, 'same object id')
// t.is(patchedFooId, fooId, 'same foo id')
// t.is(patchedBarId, barId, 'same bar id')
// t.not(patchedCopiedId, patchedFooId, 'different copied foo id')
// t.not(patchedCopiedBarId, patchedBarId, 'different copied bar id')
// })
//
test('jsonpatch move', t => {
const json = {
arr: [1,2,3],
@ -732,6 +867,54 @@ test('jsonpatch move before', t => {
})
test('jsonpatch move and replace', t => {
const json = { a: 2, b: 3 }
const patch = [
{op: 'move', from: '/a', path: '/b'},
]
const data = jsonToData(json)
const result = patchData(data, patch)
const patchedData = result.data
const revert = result.revert
const patchedJson = dataToJson(patchedData)
replaceIds(patchedData)
t.deepEqual(patchedData, {
"type": "Object",
"expanded": true,
"props": [
{
"id": "[ID]",
"name": "b",
"value": {
"type": "value",
"value": 2
}
}
]
})
t.deepEqual(patchedJson, { b : 2 })
t.deepEqual(revert, [
{op:'move', from: '/b', path: '/a'},
{op:'add', path:'/b', value: 3, jsoneditor: {type: 'value', before: 'b'}}
])
// test revert
const data2 = jsonToData(patchedJson)
const result2 = patchData(data2, revert)
const patchedData2 = result2.data
const revert2 = result2.revert
const patchedJson2 = dataToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, [
{op: 'move', from: '/a', path: '/b'}
])
})
test('jsonpatch move and replace (nested)', t => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -768,6 +951,44 @@ test('jsonpatch move and replace', t => {
])
})
test('jsonpatch move (keep id intact)', t => {
const json = { value: 42 }
const patch = [
{op: 'move', from: '/value', path: '/moved'}
]
const data = jsonToData(json)
const objectId = data.id
const valueId = data.props[0].value.id
const patchedData = patchData(data, patch).data
const patchedObjectId = patchedData.id
const patchedValueId = patchedData.props[0].value.id
t.is(patchedObjectId, objectId)
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 = jsonToData(json)
const bId = data.props[1].id
t.is(data.props[0].name, 'a')
t.is(data.props[1].name, 'b')
const patchedData = patchData(data, patch).data
const patchedBId = patchedData.props[0].id
t.is(patchedData.props[0].name, 'b')
t.is(patchedBId, bId)
})
test('jsonpatch test (ok)', t => {
const json = {
arr: [1,2,3],
@ -855,7 +1076,7 @@ test('add and remove errors', t => {
test('transform', t => {
// {obj: {a: 2}, arr: [3]}
let log = []
const transformed = transform(JSON_DATA_SMALL, function (value, path, root) {
t.is(root, JSON_DATA_SMALL)
@ -878,18 +1099,18 @@ test('transform', t => {
[JSON_DATA_SMALL.props[0].value, ['obj'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[0].value.props[0].value, ['obj', 'a'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[1].value, ['arr'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[1].value.items[0], ['arr', '0'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[1].value.items[0].value, ['arr', '0'], JSON_DATA_SMALL],
]
// log.forEach((row, index) => {
// t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
// })
log.forEach((row, index) => {
t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
})
t.deepEqual(log, EXPECTED_LOG)
t.not(transformed, JSON_DATA_SMALL)
t.not(transformed.props[0].value, JSON_DATA_SMALL.props[0].value)
t.not(transformed.props[0].value.props[0].value, JSON_DATA_SMALL.props[0].value.props[0].value)
t.is(transformed.props[1].value, JSON_DATA_SMALL.props[1].value)
t.is(transformed.props[1].value.items[0], JSON_DATA_SMALL.props[1].value.items[0])
t.is(transformed.props[1].value.items[0].value, JSON_DATA_SMALL.props[1].value.items[0].value)
})
@ -910,19 +1131,19 @@ test('traverse', t => {
[JSON_DATA_SMALL.props[0].value, ['obj'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[0].value.props[0].value, ['obj', 'a'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[1].value, ['arr'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[1].value.items[0], ['arr', '0'], JSON_DATA_SMALL],
[JSON_DATA_SMALL.props[1].value.items[0].value, ['arr', '0'], JSON_DATA_SMALL],
]
// log.forEach((row, index) => {
// t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
// })
log.forEach((row, index) => {
t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
})
t.deepEqual(log, EXPECTED_LOG)
})
test('search', t => {
const searchResults = search(JSON_DATA_EXAMPLE, 'L')
//console.log(JSON.stringify(searchResults, null, 2))
// printJSON(searchResults)
t.deepEqual(searchResults, [
{path: ['obj', 'arr', '2', 'last'], type: 'property'},
@ -935,7 +1156,7 @@ test('search', t => {
const activeSearchResult = searchResults[0]
const updatedData = addSearchResults(JSON_DATA_EXAMPLE, searchResults, activeSearchResult)
// console.log(JSON.stringify(updatedData, null, 2))
// printJSON(updatedData)
t.deepEqual(updatedData, JSON_DATA_EXAMPLE_SEARCH_L)
})
@ -995,3 +1216,28 @@ test('previousSearchResult', t => {
// return null when searchResults are empty
t.deepEqual(previousSearchResult([], {path: ['non', 'existing'], type: 'value'}), null)
})
// 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) {
console.log(message)
}
console.log(JSON.stringify(json, null, 2))
}