@@ -25,66 +25,64 @@ func DoBytes(t *testing.T, json []byte, rules []Rule) []byte {
2525 return json
2626}
2727
28- // replaceJSONInput takes a gjson path and replaces all elements the path matches with the output of matcher
29- func replaceJSONInput (t * testing.T , jsonInput []byte , path string , matcher func (toReplace gjson.Result ) any ) []byte {
28+ func expandArrayPaths (t * testing.T , jsonInput []byte , path string ) []string {
3029 t .Helper ()
3130
32- pathArray := []string {}
31+ // split on the first intermediate #, if present
32+ pathToArray , restOfPath , hasArrayPlaceholder := strings .Cut (path , ".#." )
33+
34+ // if there is no intermediate placeholder, check for (and cut) a terminal one
35+ if ! hasArrayPlaceholder {
36+ pathToArray , hasArrayPlaceholder = strings .CutSuffix (path , ".#" )
37+ }
38+
39+ // if there are no array placeholders in the path, just return it
40+ if ! hasArrayPlaceholder {
41+ return []string {path }
42+ }
43+
44+ r := gjson .GetBytes (jsonInput , pathToArray )
3345
34- // If there are more than 2 #, sjson cannot replace them directly. Iterate out all individual entries
35- if strings .Contains (path , "#" ) {
36- // Get the path ending with #
37- // E.g. results.#.packages.#.vulnerabilities => results.#.packages.#
38- numOfEntriesPath := path [:strings .LastIndex (path , "#" )+ 1 ]
39- // This returns a potentially nested array of array lengths
40- numOfEntries := gjson .GetBytes (jsonInput , numOfEntriesPath )
46+ // skip properties that are not arrays
47+ if ! r .IsArray () {
48+ return []string {}
49+ }
50+
51+ // if property exists and is actually an array, build out the path to each item
52+ // within that array
53+ paths := make ([]string , 0 , len (r .Array ()))
54+
55+ for i := range r .Array () {
56+ static := pathToArray + "." + strconv .Itoa (i )
4157
42- // Use it to build up a list of concrete paths
43- buildSJSONPaths ( t , & pathArray , path , numOfEntries )
44- } else {
45- pathArray = append (pathArray , path )
58+ if restOfPath != "" {
59+ static += "." + restOfPath
60+ }
61+ paths = append (paths , expandArrayPaths ( t , jsonInput , static ) ... )
4662 }
4763
64+ return paths
65+ }
66+
67+ // replaceJSONInput takes a gjson path and replaces all elements the path matches with the output of matcher
68+ func replaceJSONInput (t * testing.T , jsonInput []byte , path string , replacer func (toReplace gjson.Result ) any ) []byte {
69+ t .Helper ()
70+
4871 var err error
4972 json := jsonInput
50- for _ , pathElem := range pathArray {
73+ for _ , pathElem := range expandArrayPaths ( t , jsonInput , path ) {
5174 res := gjson .GetBytes (jsonInput , pathElem )
5275
5376 if ! res .Exists () {
5477 continue
5578 }
5679
57- json , err = sjson .SetBytesOptions (json , pathElem , matcher (res ), & sjson.Options {Optimistic : true })
80+ // optimistically replace the element, since we know at this point it does exist
81+ json , err = sjson .SetBytesOptions (json , pathElem , replacer (res ), & sjson.Options {Optimistic : true })
5882 if err != nil {
5983 t .Fatalf ("failed to set element" )
6084 }
6185 }
6286
6387 return json
6488}
65-
66- func buildSJSONPaths (t * testing.T , pathToBuild * []string , path string , structure gjson.Result ) {
67- t .Helper ()
68-
69- if structure .IsArray () {
70- // More nesting to go
71- for i , res := range structure .Array () {
72- buildSJSONPaths (
73- t ,
74- pathToBuild ,
75- // Replace the first # with actual index
76- strings .Replace (path , "#" , strconv .Itoa (i ), 1 ),
77- res ,
78- )
79- }
80- } else {
81- // Otherwise assume it is a number
82- if strings .Count (path , "#" ) != 1 {
83- t .Fatalf ("programmer error: there should only be 1 # left" )
84- }
85- for i2 := range int (structure .Int ()) {
86- newPath := strings .Replace (path , "#" , strconv .Itoa (i2 ), 1 )
87- * pathToBuild = append (* pathToBuild , newPath )
88- }
89- }
90- }
0 commit comments