github.com/prebid/prebid-server/v2@v2.18.0/util/jsonutil/merge.go (about)

     1  package jsonutil
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"reflect"
     7  	"unsafe"
     8  
     9  	jsoniter "github.com/json-iterator/go"
    10  	"github.com/modern-go/reflect2"
    11  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    12  
    13  	"github.com/prebid/prebid-server/v2/errortypes"
    14  	"github.com/prebid/prebid-server/v2/util/reflectutil"
    15  )
    16  
    17  // jsonConfigMergeClone uses the same configuration as the `ConfigCompatibleWithStandardLibrary` profile
    18  // with extensions added to support the merge clone behavior.
    19  var jsonConfigMergeClone = jsoniter.Config{
    20  	EscapeHTML:             true,
    21  	SortMapKeys:            true,
    22  	ValidateJsonRawMessage: true,
    23  }.Froze()
    24  
    25  func init() {
    26  	jsonConfigMergeClone.RegisterExtension(&mergeCloneExtension{})
    27  }
    28  
    29  // MergeClone unmarshals json data on top of an existing object and clones pointers of
    30  // the existing object before setting new values. Slices and maps are also cloned.
    31  // Fields of type json.RawMessage are merged rather than replaced.
    32  func MergeClone(v any, data json.RawMessage) error {
    33  	err := jsonConfigMergeClone.Unmarshal(data, v)
    34  	if err != nil {
    35  		return &errortypes.FailedToUnmarshal{
    36  			Message: tryExtractErrorMessage(err),
    37  		}
    38  	}
    39  	return err
    40  }
    41  
    42  type mergeCloneExtension struct {
    43  	jsoniter.DummyExtension
    44  }
    45  
    46  func (e *mergeCloneExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {
    47  	if typ == jsonRawMessageType {
    48  		return &extMergeDecoder{sliceType: typ.(*reflect2.UnsafeSliceType)}
    49  	}
    50  	return nil
    51  }
    52  
    53  func (e *mergeCloneExtension) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder {
    54  	if typ.Kind() == reflect.Ptr {
    55  		ptrType := typ.(*reflect2.UnsafePtrType)
    56  		return &ptrCloneDecoder{valueDecoder: decoder, elemType: ptrType.Elem()}
    57  	}
    58  
    59  	// don't use json.RawMessage on fields handled by extMergeDecoder
    60  	if typ.Kind() == reflect.Slice && typ != jsonRawMessageType {
    61  		return &sliceCloneDecoder{valueDecoder: decoder, sliceType: typ.(*reflect2.UnsafeSliceType)}
    62  	}
    63  
    64  	if typ.Kind() == reflect.Map {
    65  		return &mapCloneDecoder{valueDecoder: decoder, mapType: typ.(*reflect2.UnsafeMapType)}
    66  	}
    67  
    68  	return decoder
    69  }
    70  
    71  type ptrCloneDecoder struct {
    72  	elemType     reflect2.Type
    73  	valueDecoder jsoniter.ValDecoder
    74  }
    75  
    76  func (d *ptrCloneDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
    77  	// don't clone if field is being set to nil. checking for nil "consumes" the null
    78  	// token, so must be handled in this decoder.
    79  	if iter.ReadNil() {
    80  		*((*unsafe.Pointer)(ptr)) = nil
    81  		return
    82  	}
    83  
    84  	// clone if there is an existing object. creation of new objects is handled by the
    85  	// original decoder.
    86  	if *((*unsafe.Pointer)(ptr)) != nil {
    87  		obj := d.elemType.UnsafeNew()
    88  		d.elemType.UnsafeSet(obj, *((*unsafe.Pointer)(ptr)))
    89  		*((*unsafe.Pointer)(ptr)) = obj
    90  	}
    91  
    92  	d.valueDecoder.Decode(ptr, iter)
    93  }
    94  
    95  type sliceCloneDecoder struct {
    96  	sliceType    *reflect2.UnsafeSliceType
    97  	valueDecoder jsoniter.ValDecoder
    98  }
    99  
   100  func (d *sliceCloneDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
   101  	// don't clone if field is being set to nil. checking for nil "consumes" the null
   102  	// token, so must be handled in this decoder.
   103  	if iter.ReadNil() {
   104  		d.sliceType.UnsafeSetNil(ptr)
   105  		return
   106  	}
   107  
   108  	// clone if there is an existing object. creation of new objects is handled by the
   109  	// original decoder.
   110  	if !d.sliceType.UnsafeIsNil(ptr) {
   111  		clone := reflectutil.UnsafeSliceClone(ptr, d.sliceType)
   112  		d.sliceType.UnsafeSet(ptr, clone)
   113  	}
   114  
   115  	d.valueDecoder.Decode(ptr, iter)
   116  }
   117  
   118  type mapCloneDecoder struct {
   119  	mapType      *reflect2.UnsafeMapType
   120  	valueDecoder jsoniter.ValDecoder
   121  }
   122  
   123  func (d *mapCloneDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
   124  	// don't clone if field is being set to nil. checking for nil "consumes" the null
   125  	// token, so must be handled in this decoder.
   126  	if iter.ReadNil() {
   127  		*(*unsafe.Pointer)(ptr) = nil
   128  		d.mapType.UnsafeSet(ptr, d.mapType.UnsafeNew())
   129  		return
   130  	}
   131  
   132  	// clone if there is an existing object. creation of new objects is handled by the
   133  	// original decoder.
   134  	if !d.mapType.UnsafeIsNil(ptr) {
   135  		clone := d.mapType.UnsafeMakeMap(0)
   136  		mapIter := d.mapType.UnsafeIterate(ptr)
   137  		for mapIter.HasNext() {
   138  			key, elem := mapIter.UnsafeNext()
   139  			d.mapType.UnsafeSetIndex(clone, key, elem)
   140  		}
   141  		d.mapType.UnsafeSet(ptr, clone)
   142  	}
   143  
   144  	d.valueDecoder.Decode(ptr, iter)
   145  }
   146  
   147  type extMergeDecoder struct {
   148  	sliceType *reflect2.UnsafeSliceType
   149  }
   150  
   151  func (d *extMergeDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
   152  	// incoming nil value, keep existing
   153  	if iter.ReadNil() {
   154  		return
   155  	}
   156  
   157  	existing := *((*json.RawMessage)(ptr))
   158  	incoming := iter.SkipAndReturnBytes()
   159  
   160  	// check for read errors to avoid calling jsonpatch.MergePatch on bad data.
   161  	if iter.Error != nil {
   162  		return
   163  	}
   164  
   165  	// existing empty value, use incoming
   166  	if len(existing) == 0 {
   167  		*((*json.RawMessage)(ptr)) = incoming
   168  		return
   169  	}
   170  
   171  	// non-empty incoming and existing values, merge
   172  	merged, err := jsonpatch.MergePatch(existing, incoming)
   173  	if err != nil {
   174  		if errors.Is(err, jsonpatch.ErrBadJSONDoc) {
   175  			iter.ReportError("merge", "invalid json on existing object")
   176  		} else {
   177  			iter.ReportError("merge", err.Error())
   178  		}
   179  		return
   180  	}
   181  
   182  	*((*json.RawMessage)(ptr)) = merged
   183  }