123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- package gojsondiff
- import (
- "errors"
- dmp "github.com/sergi/go-diff/diffmatchpatch"
- "reflect"
- "strconv"
- )
- // A Delta represents an atomic difference between two JSON objects.
- type Delta interface {
- // Similarity calculates the similarity of the Delta values.
- // The return value is normalized from 0 to 1,
- // 0 is completely different and 1 is they are same
- Similarity() (similarity float64)
- }
- // To cache the calculated similarity,
- // concrete Deltas can use similariter and similarityCache
- type similariter interface {
- similarity() (similarity float64)
- }
- type similarityCache struct {
- similariter
- value float64
- }
- func newSimilarityCache(sim similariter) similarityCache {
- cache := similarityCache{similariter: sim, value: -1}
- return cache
- }
- func (cache similarityCache) Similarity() (similarity float64) {
- if cache.value < 0 {
- cache.value = cache.similariter.similarity()
- }
- return cache.value
- }
- // A Position represents the position of a Delta in an object or an array.
- type Position interface {
- // String returns the position as a string
- String() (name string)
- // CompareTo returns a true if the Position is smaller than another Position.
- // This function is used to sort Positions by the sort package.
- CompareTo(another Position) bool
- }
- // A Name is a Postition with a string, which means the delta is in an object.
- type Name string
- func (n Name) String() (name string) {
- return string(n)
- }
- func (n Name) CompareTo(another Position) bool {
- return n < another.(Name)
- }
- // A Index is a Position with an int value, which means the Delta is in an Array.
- type Index int
- func (i Index) String() (name string) {
- return strconv.Itoa(int(i))
- }
- func (i Index) CompareTo(another Position) bool {
- return i < another.(Index)
- }
- // A PreDelta is a Delta that has a position of the left side JSON object.
- // Deltas implements this interface should be applies before PostDeltas.
- type PreDelta interface {
- // PrePosition returns the Position.
- PrePosition() Position
- // PreApply applies the delta to object.
- PreApply(object interface{}) interface{}
- }
- type preDelta struct{ Position }
- func (i preDelta) PrePosition() Position {
- return Position(i.Position)
- }
- type preDeltas []PreDelta
- // for sorting
- func (s preDeltas) Len() int {
- return len(s)
- }
- // for sorting
- func (s preDeltas) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
- }
- // for sorting
- func (s preDeltas) Less(i, j int) bool {
- return !s[i].PrePosition().CompareTo(s[j].PrePosition())
- }
- // A PreDelta is a Delta that has a position of the right side JSON object.
- // Deltas implements this interface should be applies after PreDeltas.
- type PostDelta interface {
- // PostPosition returns the Position.
- PostPosition() Position
- // PostApply applies the delta to object.
- PostApply(object interface{}) interface{}
- }
- type postDelta struct{ Position }
- func (i postDelta) PostPosition() Position {
- return Position(i.Position)
- }
- type postDeltas []PostDelta
- // for sorting
- func (s postDeltas) Len() int {
- return len(s)
- }
- // for sorting
- func (s postDeltas) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
- }
- // for sorting
- func (s postDeltas) Less(i, j int) bool {
- return s[i].PostPosition().CompareTo(s[j].PostPosition())
- }
- // An Object is a Delta that represents an object of JSON
- type Object struct {
- postDelta
- similarityCache
- // Deltas holds internal Deltas
- Deltas []Delta
- }
- // NewObject returns an Object
- func NewObject(position Position, deltas []Delta) *Object {
- d := Object{postDelta: postDelta{position}, Deltas: deltas}
- d.similarityCache = newSimilarityCache(&d)
- return &d
- }
- func (d *Object) PostApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- o := object.(map[string]interface{})
- n := string(d.PostPosition().(Name))
- o[n] = applyDeltas(d.Deltas, o[n])
- case []interface{}:
- o := object.([]interface{})
- n := int(d.PostPosition().(Index))
- o[n] = applyDeltas(d.Deltas, o[n])
- }
- return object
- }
- func (d *Object) similarity() (similarity float64) {
- similarity = deltasSimilarity(d.Deltas)
- return
- }
- // An Array is a Delta that represents an array of JSON
- type Array struct {
- postDelta
- similarityCache
- // Deltas holds internal Deltas
- Deltas []Delta
- }
- // NewArray returns an Array
- func NewArray(position Position, deltas []Delta) *Array {
- d := Array{postDelta: postDelta{position}, Deltas: deltas}
- d.similarityCache = newSimilarityCache(&d)
- return &d
- }
- func (d *Array) PostApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- o := object.(map[string]interface{})
- n := string(d.PostPosition().(Name))
- o[n] = applyDeltas(d.Deltas, o[n])
- case []interface{}:
- o := object.([]interface{})
- n := int(d.PostPosition().(Index))
- o[n] = applyDeltas(d.Deltas, o[n])
- }
- return object
- }
- func (d *Array) similarity() (similarity float64) {
- similarity = deltasSimilarity(d.Deltas)
- return
- }
- // An Added represents a new added field of an object or an array
- type Added struct {
- postDelta
- similarityCache
- // Values holds the added value
- Value interface{}
- }
- // NewAdded returns a new Added
- func NewAdded(position Position, value interface{}) *Added {
- d := Added{postDelta: postDelta{position}, Value: value}
- return &d
- }
- func (d *Added) PostApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- object.(map[string]interface{})[string(d.PostPosition().(Name))] = d.Value
- case []interface{}:
- i := int(d.PostPosition().(Index))
- o := object.([]interface{})
- if i < len(o) {
- o = append(o, 0) //dummy
- copy(o[i+1:], o[i:])
- o[i] = d.Value
- object = o
- } else {
- object = append(o, d.Value)
- }
- }
- return object
- }
- func (d *Added) similarity() (similarity float64) {
- return 0
- }
- // A Modified represents a field whose value is changed.
- type Modified struct {
- postDelta
- similarityCache
- // The value before modification
- OldValue interface{}
- // The value after modification
- NewValue interface{}
- }
- // NewModified returns a Modified
- func NewModified(position Position, oldValue, newValue interface{}) *Modified {
- d := Modified{
- postDelta: postDelta{position},
- OldValue: oldValue,
- NewValue: newValue,
- }
- d.similarityCache = newSimilarityCache(&d)
- return &d
- }
- func (d *Modified) PostApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- // TODO check old value
- object.(map[string]interface{})[string(d.PostPosition().(Name))] = d.NewValue
- case []interface{}:
- object.([]interface{})[int(d.PostPosition().(Index))] = d.NewValue
- }
- return object
- }
- func (d *Modified) similarity() (similarity float64) {
- similarity += 0.3 // at least, they are at the same position
- if reflect.TypeOf(d.OldValue) == reflect.TypeOf(d.NewValue) {
- similarity += 0.3 // types are same
- switch d.OldValue.(type) {
- case string:
- similarity += 0.4 * stringSimilarity(d.OldValue.(string), d.NewValue.(string))
- case float64:
- ratio := d.OldValue.(float64) / d.NewValue.(float64)
- if ratio > 1 {
- ratio = 1 / ratio
- }
- similarity += 0.4 * ratio
- }
- }
- return
- }
- // A TextDiff represents a Modified with TextDiff between the old and the new values.
- type TextDiff struct {
- Modified
- // Diff string
- Diff []dmp.Patch
- }
- // NewTextDiff returns
- func NewTextDiff(position Position, diff []dmp.Patch, oldValue, newValue interface{}) *TextDiff {
- d := TextDiff{
- Modified: *NewModified(position, oldValue, newValue),
- Diff: diff,
- }
- return &d
- }
- func (d *TextDiff) PostApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- o := object.(map[string]interface{})
- i := string(d.PostPosition().(Name))
- // TODO error
- d.OldValue = o[i]
- // TODO error
- d.patch()
- o[i] = d.NewValue
- case []interface{}:
- o := object.([]interface{})
- i := d.PostPosition().(Index)
- d.OldValue = o[i]
- // TODO error
- d.patch()
- o[i] = d.NewValue
- }
- return object
- }
- func (d *TextDiff) patch() error {
- if d.OldValue == nil {
- return errors.New("Old Value is not set")
- }
- patcher := dmp.New()
- patched, successes := patcher.PatchApply(d.Diff, d.OldValue.(string))
- for _, success := range successes {
- if !success {
- return errors.New("Failed to apply a patch")
- }
- }
- d.NewValue = patched
- return nil
- }
- func (d *TextDiff) DiffString() string {
- dmp := dmp.New()
- return dmp.PatchToText(d.Diff)
- }
- // A Delted represents deleted field or index of an Object or an Array.
- type Deleted struct {
- preDelta
- // The value deleted
- Value interface{}
- }
- // NewDeleted returns a Deleted
- func NewDeleted(position Position, value interface{}) *Deleted {
- d := Deleted{
- preDelta: preDelta{position},
- Value: value,
- }
- return &d
- }
- func (d *Deleted) PreApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- // TODO check old value
- delete(object.(map[string]interface{}), string(d.PrePosition().(Name)))
- case []interface{}:
- i := int(d.PrePosition().(Index))
- o := object.([]interface{})
- object = append(o[:i], o[i+1:]...)
- }
- return object
- }
- func (d Deleted) Similarity() (similarity float64) {
- return 0
- }
- // A Moved represents field that is moved, which means the index or name is
- // changed. Note that, in this library, assigning a Moved and a Modified to
- // a single position is not allowed. For the compatibility with jsondiffpatch,
- // the Moved in this library can hold the old and new value in it.
- type Moved struct {
- preDelta
- postDelta
- similarityCache
- // The value before moving
- Value interface{}
- // The delta applied after moving (for compatibility)
- Delta interface{}
- }
- func NewMoved(oldPosition Position, newPosition Position, value interface{}, delta Delta) *Moved {
- d := Moved{
- preDelta: preDelta{oldPosition},
- postDelta: postDelta{newPosition},
- Value: value,
- Delta: delta,
- }
- d.similarityCache = newSimilarityCache(&d)
- return &d
- }
- func (d *Moved) PreApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- //not supported
- case []interface{}:
- i := int(d.PrePosition().(Index))
- o := object.([]interface{})
- d.Value = o[i]
- object = append(o[:i], o[i+1:]...)
- }
- return object
- }
- func (d *Moved) PostApply(object interface{}) interface{} {
- switch object.(type) {
- case map[string]interface{}:
- //not supported
- case []interface{}:
- i := int(d.PostPosition().(Index))
- o := object.([]interface{})
- o = append(o, 0) //dummy
- copy(o[i+1:], o[i:])
- o[i] = d.Value
- object = o
- }
- if d.Delta != nil {
- d.Delta.(PostDelta).PostApply(object)
- }
- return object
- }
- func (d *Moved) similarity() (similarity float64) {
- similarity = 0.6 // as type and contens are same
- ratio := float64(d.PrePosition().(Index)) / float64(d.PostPosition().(Index))
- if ratio > 1 {
- ratio = 1 / ratio
- }
- similarity += 0.4 * ratio
- return
- }
|