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

Add Runtime.ToObject() #509

Open
momiji opened this issue May 25, 2023 · 0 comments
Open

Add Runtime.ToObject() #509

momiji opened this issue May 25, 2023 · 0 comments

Comments

@momiji
Copy link

momiji commented May 25, 2023

Following our discussion on #506 about ToValue().

As proposed by @kimkit, I also tried to move to NewObject and found this is exactly what I was also looking for, much easier use that the DynamicObject.

I believe this is something that could be added, and feel that it is not making any breaking changes:

  • with obj = r.ToValue(map), map can be partially modified, as seen in cannot push value to array #506
  • with obj = r.ToObject(map), map is now readonly but obj can be fully (?) modified

Now, depending on what I'm looking for:

  • I can use ToObject() in input and then Export() to keep the modified object in input
  • I can use ToObject() only in input, and the the original value stays untouched
  • I feel I no more have usage for ToValue()...

Some tests I've made. My implementation is probably naive, let me know if I missed something really important :)

func TestGoja_ToObject(t *testing.T) {
	compile, err := goja.Compile("", `
function test(obj, state) {
	obj.a.push(3);
	obj.b[0] = 3
	obj.c = [1,2]
	obj.c.push(3)
	state.a = {}
	state.a.a = [1,2]
	state.b = [1,2]
	state.c = [1,2]
	state.a.a.push(3)
	state.b.push(3)
	state.c[0] = 3
	return obj
}
`, false)
	if err != nil {
		t.Fatalf("failed")
	}
	runtime := goja.New()
	_, err = runtime.RunProgram(compile)
	if err != nil {
		t.Fatalf("failed")
	}
	fn, _ := goja.AssertFunction(runtime.Get("test"))
	var value goja.Value

	// test output is a recognizable object
	obj := make(map[string]interface{})
	obj["a"] = []int{1, 2}
	obj["b"] = []int{1, 2}
	state := ToGojaObject(runtime, make(map[string]interface{}))

	value, err = fn(goja.Undefined(), ToGojaObject(runtime, obj), state)
	if err != nil || value.Export() == nil {
		t.Fatalf("failed")
	}
	jsonValue, _ := json.Marshal(value.Export())
	if string(jsonValue) != `{"a":[1,2,3],"b":[3,2],"c":[1,2,3]}` {
		t.Fatalf("failed")
	}

	// test input has not been modified, as expected now we have ToGojaObject
	jsonValue, _ = json.Marshal(state)
	if string(jsonValue) != `{"a":{"a":[1,2,3]},"b":[1,2,3],"c":[3,2]}` {
		t.Fatalf("failed")
	}
	jsonValue, _ = json.Marshal(state.Export())
	if string(jsonValue) != `{"a":{"a":[1,2,3]},"b":[1,2,3],"c":[3,2]}` {
		t.Fatalf("failed")
	}
}

func ToGojaObject(r *goja.Runtime, value any) goja.Value {
	t := reflect.TypeOf(value)
	switch t.Kind() {
	case reflect.Slice, reflect.Array:
		v := reflect.ValueOf(value)
		a := make([]any, v.Len())
		for i := 0; i < v.Len(); i++ {
			a[i] = ToGojaObject(r, v.Index(i).Interface())
		}
		return r.NewArray(a...)
	case reflect.Map:
		v := reflect.ValueOf(value)
		o := r.NewObject()
		iter := v.MapRange()
		for iter.Next() {
			_ = o.Set(iter.Key().Interface().(string), ToGojaObject(r, iter.Value().Interface()))
		}
		return o
	}
	return r.ToValue(value)
}
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

1 participant