Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Insert value at array indice #75

Open
wI2L opened this issue Nov 5, 2023 · 4 comments
Open

Insert value at array indice #75

wI2L opened this issue Nov 5, 2023 · 4 comments

Comments

@wI2L
Copy link

wI2L commented Nov 5, 2023

Hello @tidwall,

I have a use case where I need to "insert" a value at a specific indice in an array. However, the current behavior with SetBytes is a replacement if a value already exist at the given indice.

Consider the following example (which show the current behavior):

package main

import (
	"fmt"

	"github.com/tidwall/sjson"
)

func main() {
	var a = `["a","b","c"]`
	var b = `["a","b","c"]`

	a2, err := sjson.SetBytes([]byte(a), "0", "d")
	if err != nil {
		fmt.Println(err)
	}
	b2, err := sjson.SetBytes([]byte(b), "1", "d")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(a2))
	fmt.Println(string(b2))
}

The output is:

["d","b","c"]
["a","d","c"]

Instead, I'd like to be able to insert the values, effectively shifting all the other elements of the array to the right. The output would then be:

["d","a","b","c"]
["a","d","b","c"]

I searched for alternatives using sjson or gjson, but haven't found a way to do it with the current version of the packages.

Do you have any idea to achieve that, or would you be open to add this behavior as a feature (perhaps via a path modifier) ?

Thanks

@wI2L
Copy link
Author

wI2L commented Nov 11, 2023

Note that I'd be willing to implement this feature if we can agree on the right implementation.

@tidwall
Copy link
Owner

tidwall commented Nov 11, 2023

Here's one idea.
First use gjson to get substring information about the original item.
Then create a new json string by adding a "null" element in place of where the new item will exist.
Finally use sjson to do the replacement.

package main

import (
	"fmt"

	"github.com/tidwall/gjson"
	"github.com/tidwall/sjson"
)

func main() {
	var a = `["a","b","c"]`
	var b = `["a","b","c"]`

	a2, err := sjson.SetBytes([]byte(a), "0", "d")
	if err != nil {
		fmt.Println(err)
	}
	b2, err := sjson.SetBytes([]byte(b), "1", "d")
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(string(a2))
	fmt.Println(string(b2))

	// Here we'll insert a "dummy" null element
	res := gjson.Get(b, "1")
	if res.Index > 0 {
		b = b[:res.Index] + "null," + b[res.Index:]
	}

	// And now it works
	b2, err = sjson.SetBytes([]byte(b), "1", "d")
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(string(b2))
}
["d","b","c"]
["a","d","c"]
["a","d","b","c"]

@tidwall
Copy link
Owner

tidwall commented Nov 11, 2023

Probably the less hacky way to do it would be to extract the values into a Go array then modify that array by adding the adding the new element(s) at the index. Then reserializing the array into json.

This seems to effectively be the way the splice function in Javascript works.

Maybe adding a splice feature to sjson or gjson would make sense.

@wI2L
Copy link
Author

wI2L commented Nov 11, 2023

Thanks for your answer, and the idea.

It actually isn't very different to how I'd imagined an implementation of that feature in sjson directly:

  • fetch the position index of the item using gjson
  • if the index is > 0, add the new item in the string/byte slice directly before that position
  • otherwise, the current behavior is applied, setting the value at the index (with null eventually preprended before it)

This still require to know whether to add a , after the inserted element, if it's the last element of the array or not. I guess we could simply check if the next char is a ] signaling the end of the array. Actually, if r.Index > 0, then the array item exists, and we prepend a new item before it, so the comma must be inserted every time.

The "insert" behavior could be enabled using a new field in the sjson.Options struct.

Regarding you latter comment, this would inherently produce an allocation to hold the deserialized elements of the array, which I would avoid if I could, but that's debatable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants