Skip to content

Instantly share code, notes, and snippets.

@peter
Last active September 22, 2017 12:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peter/d5bff30c7937f0e7761b6fde5f2dbc57 to your computer and use it in GitHub Desktop.
Save peter/d5bff30c7937f0e7761b6fde5f2dbc57 to your computer and use it in GitHub Desktop.
TypeScript code to diff two JavaScript objects containing JSON data
const R = require('ramda')
function pathString(path: string[]): string {
return path.join('.')
}
function valueType(value: any): string {
if (value === undefined) {
return 'undefined'
} else if (value === null) {
return 'null'
} else if (Array.isArray(value)) {
return 'array'
} else {
// object, string, number, boolean
return typeof value
}
}
// Diff two JSON type objects. Examples:
// _diff({foo: 'bar'}, {foo: 'baz'}, [])
// { foo: { changed: { from: 'bar', to: 'baz' } } }
//
// _diff({a: {foo: true}}, {a: {foo: 'baz', bar: 1}}, [])
// { 'a.bar': { added: 1 },
// 'a.foo':
// { changed: { from: true, to: 'baz' },
// type_changed: { from: 'boolean', to: 'string' } } }
// _diff({a: {foo: [true, 'b', 'c']}}, {a: {foo: ['baz', 'b', 'c', 'd'], bar: 1}}, [])
// _diff({a: {foo: [true, 'b', 'c']}}, {a: {foo: ['baz', 'b'], bar: 1}}, [])
function _diff(v1: any, v2: any, path: string[]): Object {
const t1 = valueType(v1)
const t2 = valueType(v2)
if (t1 !== t2) {
return {
[pathString(path)]: {
changed: {
from: v1,
to: v2
},
type_changed: {
from: t1,
to: t2
}
}
}
}
if (t1 === 'array') {
let arrayDiffs: Object[] = []
if (v1.length > v2.length) {
// deleted items
arrayDiffs = R.range(v2.length, v1.length).map((index: number) => {
return {
[pathString(R.append(index, path))]: {deleted: v1[index]}
}
})
} else if (v1.length < v2.length) {
// added items
arrayDiffs = R.range(v1.length, v2.length).map((index: number) => {
return {
[pathString(R.append(index, path))]: {added: v2[index]}
}
})
}
// changed items
const diffLength = Math.min(v1.length, v2.length)
arrayDiffs = R.concat(arrayDiffs, R.range(0, diffLength).map((index: number) => {
return _diff(v1[index], v2[index], R.append(index, path))
}))
return arrayDiffs.reduce(R.merge, {})
} else if (t1 === 'object') {
const keys1 = Object.keys(v1)
const keys2 = Object.keys(v2)
const addDiffs = R.difference(keys2, keys1).map((key: string) => {
return {
[pathString(R.append(key, path))]: {added: v2[key]}
}
})
const deleteDiffs = R.difference(keys1, keys2).map((key: string) => {
return {
[pathString(R.append(key, path))]: {deleted: v1[key]}
}
})
const changeDiffs = R.intersection(keys1, keys2).map((key: string) => {
return _diff(v1[key], v2[key], R.append(key, path))
})
return R.flatten([addDiffs, deleteDiffs, changeDiffs]).reduce(R.merge, {})
} else {
if (v1 !== v2) {
return {
[pathString(path)]: {
changed: {
from: v1,
to: v2
}
}
}
} else {
return {}
}
}
}
export default function diff(obj1: Object, obj2: Object): Object {
const result = _diff(obj1, obj2, [])
return R.isEmpty(result) ? undefined : result
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment