github.com/prebid/prebid-server@v0.275.0/util/jsonutil/jsonutil.go (about)

     1  package jsonutil
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  )
     8  
     9  var comma = []byte(",")[0]
    10  var colon = []byte(":")[0]
    11  var sqBracket = []byte("]")[0]
    12  var openCurlyBracket = []byte("{")[0]
    13  var closingCurlyBracket = []byte("}")[0]
    14  var quote = []byte(`"`)[0]
    15  
    16  // Finds element in json byte array with any level of nesting
    17  func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) {
    18  	elementName := elementNames[0]
    19  	buf := bytes.NewBuffer(extension)
    20  	dec := json.NewDecoder(buf)
    21  	found := false
    22  	var startIndex, endIndex int64
    23  	var i interface{}
    24  	for {
    25  		token, err := dec.Token()
    26  		if err == io.EOF {
    27  			// io.EOF is a successful end
    28  			break
    29  		}
    30  		if err != nil {
    31  			return false, -1, -1, err
    32  		}
    33  		if token == elementName {
    34  			err := dec.Decode(&i)
    35  			if err != nil {
    36  				return false, -1, -1, err
    37  			}
    38  			endIndex = dec.InputOffset()
    39  
    40  			if dec.More() {
    41  				//if there were other elements before
    42  				if extension[startIndex] == comma {
    43  					startIndex++
    44  				}
    45  				for {
    46  					//structure has more elements, need to find index of comma
    47  					if extension[endIndex] == comma {
    48  						endIndex++
    49  						break
    50  					}
    51  					endIndex++
    52  				}
    53  			}
    54  			found = true
    55  			break
    56  		} else {
    57  			startIndex = dec.InputOffset()
    58  		}
    59  	}
    60  	if found {
    61  		if len(elementNames) == 1 {
    62  			return found, startIndex, endIndex, nil
    63  		} else if len(elementNames) > 1 {
    64  			for {
    65  				//find the beginning of nested element
    66  				if extension[startIndex] == colon {
    67  					startIndex++
    68  					break
    69  				}
    70  				startIndex++
    71  			}
    72  			for {
    73  				if endIndex == int64(len(extension)) {
    74  					endIndex--
    75  				}
    76  
    77  				//if structure had more elements, need to find index of comma at the end
    78  				if extension[endIndex] == sqBracket || extension[endIndex] == closingCurlyBracket {
    79  					break
    80  				}
    81  
    82  				if extension[endIndex] == comma {
    83  					endIndex--
    84  					break
    85  				} else {
    86  					endIndex--
    87  				}
    88  			}
    89  			if found {
    90  				found, startInd, endInd, err := FindElement(extension[startIndex:endIndex], elementNames[1:]...)
    91  				return found, startIndex + startInd, startIndex + endInd, err
    92  			}
    93  			return found, startIndex, startIndex, nil
    94  		}
    95  	}
    96  	return found, startIndex, endIndex, nil
    97  }
    98  
    99  // Drops element from json byte array
   100  // - Doesn't support drop element from json list
   101  // - Keys in the path can skip levels
   102  // - First found element will be removed
   103  func DropElement(extension []byte, elementNames ...string) ([]byte, error) {
   104  	found, startIndex, endIndex, err := FindElement(extension, elementNames...)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	if found {
   109  		extension = append(extension[:startIndex], extension[endIndex:]...)
   110  	}
   111  	return extension, nil
   112  }