ohler55 / ojg

Optimized JSON for Go
MIT License
857 stars 49 forks source link

Question: Using jp.Set() to set non-existent slice indices #131

Closed jfosdick closed 1 year ago

jfosdick commented 1 year ago

Hi there,

I have a question about jp.Set(). Is there any way to set a value in a slice, where the target index does not yet exist in the slice? I thought this would be the case from the documentation

https://pkg.go.dev/github.com/ohler55/ojg@v1.18.5/jp#Expr.Set
...If the path to the child does not exist array and map elements are added.

and from the following testcase in jp/set_test.go

https://github.com/ohler55/ojg/blob/develop/jp/set_test.go#L96
{path: "a[0]", data: `{}`, value: 3, expect: `{"a":[3]}`}

but in actuality, I'm having trouble getting it working. Here's an example code snippet that maybe better demonstrates what I'm trying to do, with a few variations of what I've tried:

package main

import (
    "encoding/json"
    "fmt"

    "github.com/ohler55/ojg/jp"
)

type Foo struct {
    Items []string
}

func main() {
    expression := jp.MustParseString("$.Items[3]")
    foo := Foo{Items: []string{"a", "b", "c"}}
    expression.MustSet(foo, "d")
    jBytes, _ := json.Marshal(foo)
    fmt.Println(string(jBytes))
    // expect: {"Items":["a","b","c","d"]}

    expression2 := jp.MustParseString("$.Items[0]")
    bar := Foo{Items: []string{}}
    expression2.MustSet(bar, "d")
    jBytes2, _ := json.Marshal(bar)
    fmt.Println(string(jBytes2))
    // expect: {"Items":["d"]}

    expression3 := jp.MustParseString("$.Items[0]")
    baz := Foo{}
    expression3.MustSet(baz, "d")
    jBytes3, _ := json.Marshal(baz)
    fmt.Println(string(jBytes3))
    // expect: {"Items":["d"]}

    expression4 := jp.MustParseString("$.Items[0]")
    bat := make(map[string]string, 1)
    expression4.MustSet(bat, "d")
    jBytes4, _ := json.Marshal(bat)
    fmt.Println(string(jBytes4))
    // expect: {"Items":["d"]}
}

/*
Expected Output:
{"Items":["a","b","c","d"]}
{"Items":["d"]}
{"Items":["d"]}
{"Items":["d"]}

Actual Output:
{"Items":["a","b","c"]}
{"Items":[]}
{"Items":null}
{}
*/

Thanks!

ohler55 commented 1 year ago

Unlike maps, slices can not be added to but instead are replaced by a new slice. That's not exactly true but it should be assumed that is the case. That is why set can not be used to grow a slice. There is an alternative though. jp.Modify() is that you are looking for. It is describe at https://github.com/ohler55/ojg/blob/develop/jp/modify.go.

jfosdick commented 1 year ago

@ohler55 This makes sense -- I have instead adapted my use case so that I can always assume the target index exists in the slice, and therefore, a simple call to jp.Set() will work to replace the existing element instead of attempt to grow the slice. This appears to be working for me, but I will keep jp.Modify() in mind in case I need to support any more advanced scenarios.

This is an awesome project, thank you!