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  }