github.com/altipla-consulting/ravendb-go-client@v0.1.3/json_operation.go (about) 1 package ravendb 2 3 import ( 4 "fmt" 5 "sort" 6 ) 7 8 func jsonOperationEntityChanged(newObj map[string]interface{}, documentInfo *documentInfo, changes map[string][]*DocumentsChanges) bool { 9 var docChanges []*DocumentsChanges 10 11 doc := documentInfo.document 12 if !documentInfo.newDocument && doc != nil { 13 id := documentInfo.id 14 return jsonOperationCompareJson(id, doc, newObj, changes, &docChanges) 15 } 16 17 if changes == nil { 18 return true 19 } 20 21 jsonOperationNewChange("", nil, nil, &docChanges, DocumentChangeDocumentAdded) 22 id := documentInfo.id 23 a := changes[id] 24 a = append(a, docChanges...) 25 changes[id] = a 26 return true 27 } 28 29 func isJSONFloatEqual(oldPropVal float64, newProp interface{}) bool { 30 switch newPropVal := newProp.(type) { 31 case float64: 32 return oldPropVal == newPropVal 33 } 34 return false 35 } 36 37 func isJSONBoolEqual(oldPropVal bool, newProp interface{}) bool { 38 switch newPropVal := newProp.(type) { 39 case bool: 40 return oldPropVal == newPropVal 41 } 42 return false 43 } 44 45 func isJSONStringEqual(oldPropVal string, newProp interface{}) bool { 46 switch newPropVal := newProp.(type) { 47 case string: 48 return oldPropVal == newPropVal 49 } 50 return false 51 } 52 53 func jsonOperationCompareJson(id string, originalJson map[string]interface{}, newJson map[string]interface{}, changes map[string][]*DocumentsChanges, docChanges *[]*DocumentsChanges) bool { 54 newJsonProps := getObjectNodeFieldNames(newJson) 55 oldJsonProps := getObjectNodeFieldNames(originalJson) 56 newFields := stringArraySubtract(newJsonProps, oldJsonProps) 57 removedFields := stringArraySubtract(oldJsonProps, newJsonProps) 58 59 for _, field := range removedFields { 60 if changes == nil { 61 return true 62 } 63 jsonOperationNewChange(field, nil, nil, docChanges, DocumentChangeRemovedField) 64 } 65 66 for _, prop := range newJsonProps { 67 switch prop { 68 case MetadataLastModified, 69 MetadataCollection, 70 MetadataChangeVector, 71 MetadataID: 72 continue 73 } 74 if stringArrayContains(newFields, prop) { 75 if changes == nil { 76 return true 77 } 78 v := newJson[prop] 79 jsonOperationNewChange(prop, v, nil, docChanges, DocumentChangeNewField) 80 continue 81 } 82 newProp := newJson[prop] 83 oldProp := originalJson[prop] 84 switch newPropVal := newProp.(type) { 85 case float64: 86 if isJSONFloatEqual(newPropVal, oldProp) { 87 break 88 } 89 if changes == nil { 90 return true 91 } 92 jsonOperationNewChange(prop, newProp, oldProp, docChanges, DocumentChangeFieldChanged) 93 case string: 94 if isJSONStringEqual(newPropVal, oldProp) { 95 break 96 } 97 if changes == nil { 98 return true 99 } 100 jsonOperationNewChange(prop, newProp, oldProp, docChanges, DocumentChangeFieldChanged) 101 case bool: 102 isJSONBoolEqual(newPropVal, oldProp) 103 case []interface{}: 104 if oldProp == nil || !isInstanceOfArrayOfInterface(oldProp) { 105 if changes == nil { 106 return true 107 } 108 109 jsonOperationNewChange(prop, newProp, oldProp, docChanges, DocumentChangeFieldChanged) 110 break 111 } 112 113 changed := jsonOperationCompareJsonArray(id, oldProp.([]interface{}), newProp.([]interface{}), changes, docChanges, prop) 114 if changes == nil && changed { 115 return true 116 } 117 118 case map[string]interface{}: 119 oldPropVal, ok := oldProp.(map[string]interface{}) 120 // TODO: a better check for nil? 121 if !ok || oldProp == nil { 122 if changes == nil { 123 return true 124 } 125 jsonOperationNewChange(prop, newProp, nil, docChanges, DocumentChangeFieldChanged) 126 break 127 } 128 changed := jsonOperationCompareJson(id, oldPropVal, newPropVal, changes, docChanges) 129 if changes == nil && changed { 130 return true 131 } 132 default: 133 if newProp == nil { 134 if oldProp == nil { 135 break 136 } 137 if changes == nil { 138 return true 139 } 140 jsonOperationNewChange(prop, nil, oldProp, docChanges, DocumentChangeFieldChanged) 141 break 142 } 143 // TODO: array, nil 144 // Write tests for all types 145 panicIf(true, "unhandled type %T, newProp: '%v', oldProp: '%v'", newProp, newProp, oldProp) 146 } 147 } 148 149 if changes == nil || len(*docChanges) == 0 { 150 return false 151 } 152 changes[id] = *docChanges 153 return true 154 } 155 156 func isInstanceOfArrayOfInterface(v interface{}) bool { 157 _, ok := v.([]interface{}) 158 return ok 159 } 160 161 func jsonOperationCompareJsonArray(id string, oldArray []interface{}, newArray []interface{}, changes map[string][]*DocumentsChanges, docChanges *[]*DocumentsChanges, propName string) bool { 162 // if we don't care about the changes 163 if len(oldArray) != len(newArray) && changes == nil { 164 return true 165 } 166 167 changed := false 168 169 position := 0 170 maxPos := len(oldArray) 171 if maxPos > len(newArray) { 172 maxPos = len(newArray) 173 } 174 for ; position < maxPos; position++ { 175 oldVal := oldArray[position] 176 newVal := newArray[position] 177 switch oldVal.(type) { 178 case map[string]interface{}: 179 if _, ok := newVal.(map[string]interface{}); ok { 180 newChanged := jsonOperationCompareJson(id, oldVal.(map[string]interface{}), newVal.(map[string]interface{}), changes, docChanges) 181 if newChanged { 182 changed = newChanged 183 } 184 } else { 185 changed = true 186 if changes != nil { 187 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueAdded) 188 } 189 } 190 case []interface{}: 191 if _, ok := newVal.([]interface{}); ok { 192 newChanged := jsonOperationCompareJsonArray(id, oldVal.([]interface{}), newVal.([]interface{}), changes, docChanges, propName) 193 if newChanged { 194 changed = newChanged 195 } 196 } else { 197 changed = true 198 if changes != nil { 199 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueChanged) 200 } 201 } 202 default: 203 // NULL case 204 if oldVal == nil { 205 if newVal != nil { 206 changed = true 207 if changes != nil { 208 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueChanged) 209 } 210 } 211 break 212 } 213 // Note: this matches Java but also means that 1 == "1" 214 oldValStr := fmt.Sprintf("%v", oldVal) 215 newValStr := fmt.Sprintf("%v", newVal) 216 if oldValStr != newValStr { 217 if changes != nil { 218 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueChanged) 219 } 220 changed = true 221 } 222 223 } 224 } 225 226 if changes == nil { 227 return changed 228 } 229 230 // if one of the arrays is larger than the other 231 for ; position < len(oldArray); position++ { 232 jsonOperationNewChange(propName, nil, oldArray[position], docChanges, DocumentChangeArrayValueRemoved) 233 } 234 235 for ; position < len(newArray); position++ { 236 jsonOperationNewChange(propName, newArray[position], nil, docChanges, DocumentChangeArrayValueAdded) 237 } 238 239 return changed 240 } 241 242 func jsonOperationNewChange(name string, newValue interface{}, oldValue interface{}, docChanges *[]*DocumentsChanges, change ChangeType) { 243 documentsChanges := &DocumentsChanges{ 244 FieldNewValue: newValue, 245 FieldOldValue: oldValue, 246 FieldName: name, 247 Change: change, 248 } 249 *docChanges = append(*docChanges, documentsChanges) 250 } 251 252 func getObjectNodeFieldNames(o map[string]interface{}) []string { 253 n := len(o) 254 if n == 0 { 255 return nil 256 } 257 res := make([]string, n) 258 i := 0 259 for k := range o { 260 res[i] = k 261 i++ 262 } 263 // Go randomizes order of map traversal but it's useful to have it 264 // fixed e.g. for tests 265 sort.Strings(res) 266 return res 267 }