dop251 / goja

ECMAScript/JavaScript engine in pure Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

New value doesn't exist when pushed to slice within map

BrianLeishman opened this issue · comments

Go program example:

package main

import (
	"fmt"

	"github.com/dop251/goja"

	_ "embed"
)

//go:embed handler.js
var js string

type Data struct {
	Map map[string]any `json:"map"`
}

func main() {
	vm := goja.New()
	vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
	_, err := vm.RunString(js)
	if err != nil {
		panic(err)
	}

	var handler func(data *Data) any
	err = vm.ExportTo(vm.Get("handler"), &handler)
	if err != nil {
		panic(err)
	}

	data := &Data{
		Map: map[string]any{"slice": []any{"foo"}},
	}

	handler(data)

	fmt.Println(data)
}

handler.js

function handler(data) {
    data.map.slice.push("bar");
}

As you can see in the js, I push a value onto the slice within the map. I would expect the output of this program to be

&{map[slice:[foo bar]]}

But instead, I get this

&{map[slice:[foo]]}

It works as expected if I rewrite my js to look like the following

function handler(data) {
    const slice = data.map.slice;
    slice.push("bar");
    data.map.slice = slice;
}

Yeah, I would expect that too. Unfortunately, this is not how Go works. Values in maps are not addressable, so when you take it out of a map you get a copy. Your code is equivalent to

	m := make(map[string]any)
	m["slice"] = []any{"foo"}
	s := m["slice"].([]any)
	s = append(s, "bar")

	fmt.Println(m["slice"])

The solution is to always store non-primitive values as pointers in maps.

This behaviour is documented, see https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue