github.com/prebid/prebid-server/v2@v2.18.0/util/jsonutil/jsonutil.go (about) 1 package jsonutil 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "strings" 8 "unsafe" 9 10 jsoniter "github.com/json-iterator/go" 11 "github.com/modern-go/reflect2" 12 "github.com/prebid/prebid-server/v2/errortypes" 13 ) 14 15 var comma = byte(',') 16 var colon = byte(':') 17 var sqBracket = byte(']') 18 var closingCurlyBracket = byte('}') 19 20 // Finds element in json byte array with any level of nesting 21 func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) { 22 elementName := elementNames[0] 23 buf := bytes.NewBuffer(extension) 24 dec := json.NewDecoder(buf) 25 found := false 26 var startIndex, endIndex int64 27 var i interface{} 28 for { 29 token, err := dec.Token() 30 if err == io.EOF { 31 // io.EOF is a successful end 32 break 33 } 34 if err != nil { 35 return false, -1, -1, err 36 } 37 if token == elementName { 38 err := dec.Decode(&i) 39 if err != nil { 40 return false, -1, -1, err 41 } 42 endIndex = dec.InputOffset() 43 44 if dec.More() { 45 //if there were other elements before 46 if extension[startIndex] == comma { 47 startIndex++ 48 } 49 for { 50 //structure has more elements, need to find index of comma 51 if extension[endIndex] == comma { 52 endIndex++ 53 break 54 } 55 endIndex++ 56 } 57 } 58 found = true 59 break 60 } else { 61 startIndex = dec.InputOffset() 62 } 63 } 64 if found { 65 if len(elementNames) == 1 { 66 return found, startIndex, endIndex, nil 67 } else if len(elementNames) > 1 { 68 for { 69 //find the beginning of nested element 70 if extension[startIndex] == colon { 71 startIndex++ 72 break 73 } 74 startIndex++ 75 } 76 for { 77 if endIndex == int64(len(extension)) { 78 endIndex-- 79 } 80 81 //if structure had more elements, need to find index of comma at the end 82 if extension[endIndex] == sqBracket || extension[endIndex] == closingCurlyBracket { 83 break 84 } 85 86 if extension[endIndex] == comma { 87 endIndex-- 88 break 89 } else { 90 endIndex-- 91 } 92 } 93 if found { 94 found, startInd, endInd, err := FindElement(extension[startIndex:endIndex], elementNames[1:]...) 95 return found, startIndex + startInd, startIndex + endInd, err 96 } 97 return found, startIndex, startIndex, nil 98 } 99 } 100 return found, startIndex, endIndex, nil 101 } 102 103 // Drops element from json byte array 104 // - Doesn't support drop element from json list 105 // - Keys in the path can skip levels 106 // - First found element will be removed 107 func DropElement(extension []byte, elementNames ...string) ([]byte, error) { 108 found, startIndex, endIndex, err := FindElement(extension, elementNames...) 109 if err != nil { 110 return nil, err 111 } 112 if found { 113 extension = append(extension[:startIndex], extension[endIndex:]...) 114 } 115 return extension, nil 116 } 117 118 // jsonConfigValidationOn attempts to maintain compatibility with the standard library which 119 // includes enabling validation 120 var jsonConfigValidationOn = jsoniter.ConfigCompatibleWithStandardLibrary 121 122 // jsonConfigValidationOff disables validation 123 var jsonConfigValidationOff = jsoniter.Config{ 124 EscapeHTML: true, 125 SortMapKeys: true, 126 ValidateJsonRawMessage: false, 127 }.Froze() 128 129 // Unmarshal unmarshals a byte slice into the specified data structure without performing 130 // any validation on the data. An unmarshal error is returned if a non-validation error occurs. 131 func Unmarshal(data []byte, v interface{}) error { 132 err := jsonConfigValidationOff.Unmarshal(data, v) 133 if err != nil { 134 return &errortypes.FailedToUnmarshal{ 135 Message: tryExtractErrorMessage(err), 136 } 137 } 138 return nil 139 } 140 141 // UnmarshalValid validates and unmarshals a byte slice into the specified data structure 142 // returning an error if validation fails 143 func UnmarshalValid(data []byte, v interface{}) error { 144 if err := jsonConfigValidationOn.Unmarshal(data, v); err != nil { 145 return &errortypes.FailedToUnmarshal{ 146 Message: tryExtractErrorMessage(err), 147 } 148 } 149 return nil 150 } 151 152 // Marshal marshals a data structure into a byte slice without performing any validation 153 // on the data. A marshal error is returned if a non-validation error occurs. 154 func Marshal(v interface{}) ([]byte, error) { 155 data, err := jsonConfigValidationOn.Marshal(v) 156 if err != nil { 157 return nil, &errortypes.FailedToMarshal{ 158 Message: err.Error(), 159 } 160 } 161 return data, nil 162 } 163 164 // tryExtractErrorMessage attempts to extract a sane error message from the json-iter package. The errors 165 // returned from that library are not types and include a lot of extra information we don't want to respond with. 166 // This is hacky, but it's the only downside to the json-iter library. 167 func tryExtractErrorMessage(err error) string { 168 msg := err.Error() 169 170 msgEndIndex := strings.LastIndex(msg, ", error found in #") 171 if msgEndIndex == -1 { 172 return msg 173 } 174 175 msgStartIndex := strings.Index(msg, ": ") 176 if msgStartIndex == -1 { 177 return msg 178 } 179 180 operationStack := []string{msg[0:msgStartIndex]} 181 for { 182 msgStartIndexNext := strings.Index(msg[msgStartIndex+2:], ": ") 183 184 // no more matches 185 if msgStartIndexNext == -1 { 186 break 187 } 188 189 // matches occur after the end message marker (sanity check) 190 if (msgStartIndex + msgStartIndexNext) >= msgEndIndex { 191 break 192 } 193 194 // match should not contain a space, indicates operation is really an error message 195 match := msg[msgStartIndex+2 : msgStartIndex+2+msgStartIndexNext] 196 if strings.Contains(match, " ") { 197 break 198 } 199 200 operationStack = append(operationStack, match) 201 msgStartIndex += msgStartIndexNext + 2 202 } 203 204 if len(operationStack) > 1 && isLikelyDetailedErrorMessage(msg[msgStartIndex+2:]) { 205 return "cannot unmarshal " + operationStack[len(operationStack)-2] + ": " + msg[msgStartIndex+2:msgEndIndex] 206 } 207 208 return msg[msgStartIndex+2 : msgEndIndex] 209 } 210 211 // isLikelyDetailedErrorMessage checks if the json unmarshal error contains enough information such 212 // that the caller clearly understands the context, where the structure name is not needed. 213 func isLikelyDetailedErrorMessage(msg string) bool { 214 return !strings.HasPrefix(msg, "request.") 215 } 216 217 // RawMessageExtension will call json.Compact() on every json.RawMessage field when getting marshalled. 218 type RawMessageExtension struct { 219 jsoniter.DummyExtension 220 } 221 222 // CreateEncoder substitutes the default jsoniter encoder of the json.RawMessage type with ours, that 223 // calls json.Compact() before writting to the stream 224 func (e *RawMessageExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { 225 if typ == jsonRawMessageType { 226 return &rawMessageCodec{} 227 } 228 return nil 229 } 230 231 var jsonRawMessageType = reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem() 232 233 // rawMessageCodec implements jsoniter.ValEncoder interface so we can override the default json.RawMessage Encode() 234 // function with our implementation 235 type rawMessageCodec struct{} 236 237 func (codec *rawMessageCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { 238 if ptr != nil { 239 jsonRawMsg := *(*[]byte)(ptr) 240 241 dst := bytes.NewBuffer(make([]byte, 0, len(jsonRawMsg))) 242 if err := json.Compact(dst, jsonRawMsg); err == nil { 243 stream.Write(dst.Bytes()) 244 } 245 } 246 } 247 248 func (codec *rawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool { 249 return ptr == nil || len(*((*json.RawMessage)(ptr))) == 0 250 }