Sorting a struct slice by a compare method removes and duplicates items
jmattheis opened this issue · comments
The return value of the sort + map somehow returns 3x the bin entry and omits the log/test thing.
package main
import (
"fmt"
"github.com/dop251/goja"
)
type Thing struct { Name string `json:"name"` }
func main() {
vm := goja.New()
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
vm.Set("read", func(call goja.FunctionCall) goja.Value {
return vm.ToValue([]Thing{
{Name: "log"},
{Name: "etc"},
{Name: "test"},
{Name: "bin"},
})
})
// This one works
// vm.Set("read", func(call goja.FunctionCall) goja.Value {
// x := func(name string) goja.Value {
// o := vm.NewObject()
// o.Set("name", name)
// return o
// }
// return vm.NewArray(x("log"), x("etc"), x("test"), x("bin"))
// })
ret, err := vm.RunString(`
read().sort((a, b) => a.name.localeCompare(b.name)).map((x) => x.name);
`)
if err != nil {
panic(err)
}
fmt.Println(ret.Export())
// outputs: [bin etc bin bin]
}
This is a very tricky one... With the current implementation (after #378) accessing an array or a struct element of a wrapped Go reflect value returns a reference to that element, not a copy. So when you do this:
a[0].name = '0';
a[1].name = '1';
let tmp = a[0];
a[0] = a[1];
tmp.name
will be '1'., because assigning to a[0]
changed the referenced value (which tmp
also referring to).
Fixing this is non-trivial, because if a[0]
returned a copy rather than a reference, a[0].name = '...
' would not work as expected (it would create a copy of a[0]
, set its name and immediately lose the copy). Not to mention that any dereference (even just for reading a property) would result in a copy.
Same thing applies to nested structs, and considering anonymous fields are also nested, the effects would be even more bizarre.
It's easy to fix it so that it at least works for pointers (i.e. it would work for []*Thing
), but with non-pointers the only thing I can think of is some kind of copy-on-write mechanism which would require to have a cache of already accessed array elements and struct fields and changing their underlying reflect value to a copy in case the element is overwritten.
If anyone has any suggestions, let me know.
Ok, I think this should sort it (and hopefully shouldn't break anything). Feedback is welcome as usual.