github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/internal/runner/experiment_json.go (about) 1 // Copyright 2018-2020 (c) Cognizant Digital Business, Evolutionary AI. All rights reserved. Issued under the Apache 2.0 License. 2 3 package runner 4 5 // This file contains functions capable of processing experiment json structures 6 7 import ( 8 "encoding/json" 9 "strings" 10 11 "github.com/go-stack/stack" 12 "github.com/jjeffery/kv" // MIT License 13 14 jsonpatch "github.com/evanphx/json-patch" 15 ) 16 17 // MergeExperiment merges the two JSON-marshalable values x1 and x2, 18 // preferring x1 over x2 except where x1 and x2 are 19 // JSON objects, in which case the keys from both objects 20 // are included and their values merged recursively. 21 // 22 // It returns an error if x1 or x2 cannot be JSON-marshaled. 23 // 24 func MergeExperiment(x1, x2 interface{}) (interface{}, kv.Error) { 25 data1, errGo := json.Marshal(x1) 26 if errGo != nil { 27 return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 28 } 29 data2, errGo := json.Marshal(x2) 30 if errGo != nil { 31 return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 32 } 33 var j1 interface{} 34 errGo = json.Unmarshal(data1, &j1) 35 if errGo != nil { 36 return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 37 } 38 var j2 interface{} 39 errGo = json.Unmarshal(data2, &j2) 40 if errGo != nil { 41 return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 42 } 43 return mergeMaps(j1, j2), nil 44 } 45 46 func mergeMaps(x1, x2 interface{}) interface{} { 47 switch x1 := x1.(type) { 48 case map[string]interface{}: 49 x2, ok := x2.(map[string]interface{}) 50 if !ok { 51 return x1 52 } 53 for k, v2 := range x2 { 54 if v1, ok := x1[k]; ok { 55 x1[k] = mergeMaps(v1, v2) 56 } else { 57 x1[k] = v2 58 } 59 } 60 case nil: 61 // merge(nil, map[string]interface{...}) -> map[string]interface{...} 62 x2, ok := x2.(map[string]interface{}) 63 if ok { 64 return x2 65 } 66 } 67 return x1 68 } 69 70 // ExtractMergeDoc uses two JSON-marshalable values x1 and x2 performing a 71 // merge and returns the results 72 func ExtractMergeDoc(x1, x2 interface{}) (results string, err kv.Error) { 73 x3, err := MergeExperiment(x1, x2) 74 if err != nil { 75 return "", err 76 } 77 78 data, errGo := json.MarshalIndent(x3, "", "\t") 79 if errGo != nil { 80 return "", kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 81 } 82 83 lines := []string{} 84 for _, aLine := range strings.Split(string(data), "\n") { 85 lines = append(lines, strings.TrimSpace(aLine)) 86 } 87 88 return strings.Join(lines, " "), nil 89 } 90 91 // JSONEditor will accept a source JSON document and an array of change edits 92 // for the source document and will process them as either RFC7386, or RFC6902 edits 93 // if they validate as either. 94 // 95 func JSONEditor(oldDoc string, directives []string) (result string, err kv.Error) { 96 97 doc := []byte(oldDoc) 98 99 if len(doc) == 0 { 100 doc = []byte(`{}`) 101 } 102 103 for _, directive := range directives { 104 patch, errGo := jsonpatch.DecodePatch([]byte(directive)) 105 if errGo == nil { 106 if doc, errGo = patch.Apply(doc); errGo != nil { 107 return "", kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 108 } 109 } else { 110 var edit interface{} 111 if errGo = json.Unmarshal([]byte(directive), &edit); errGo != nil { 112 return "", kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 113 } 114 var sourceDoc interface{} 115 if errGo = json.Unmarshal([]byte(doc), &sourceDoc); errGo != nil { 116 return "", kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 117 } 118 extracted, err := ExtractMergeDoc(&edit, &sourceDoc) 119 if err != nil { 120 return "", kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()) 121 } 122 doc = []byte(extracted) 123 } 124 } 125 126 return string(doc), nil 127 }