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

passing a complex object from js to go function does not unmarshal all fields #4032

Open
scottkgaran opened this issue Oct 30, 2024 · 1 comment
Assignees

Comments

@scottkgaran
Copy link

scottkgaran commented Oct 30, 2024

Brief summary

Hi all, I am currently writing a k6 extension.

I am trying to pass a "complex" object from js to my go function. But for some reason I do not get back all the fields on the go side.

I have noticed this with structs within structs as well as type alias's.

import magic from 'k6/x/magic';

export default function () {
  const vars = {
    order: {
      id: 'wakka',
      sku: {
        id: 'magic'
      }
    },
    superStatus: 'XLARGE',
    countryPopStar: {
      id: '9001'
    },
    normal: 'string'
  };

  magic(vars);
}
type Order struct {
	Id  string `json:"id"`
	Sku SKU    `json:"sku"`
}

type SKU struct {
	Id string `json:"id"`
}

type CountryPopStar struct {
	Id string `json:"id"`
}

type SuperStatus string

type TestStruct struct {
	*Order          `json:"order,omitempty"`
	*SuperStatus    `json:"superStatus"`
	*CountryPopStar `json:"countryPopStar"`
	Normal          *string `json:"normal"`
}

func Magic(ts *TestStruct) {
	logger.Info(ts.String())
}

Happy to create a repro repo if needed.

k6 version

latest, master

OS

windows wsl (arch)

Docker version and image (if applicable)

No response

Steps to reproduce the problem

  1. write an extension that has some complex structs and create a function that uses that struct as an argument
  2. call that function from your script and pass in the js object
  3. notice not all fields get deserialized

Do the same thing using sobek runtime directly (via test):

  1. take your js script and pass it in as a string to RunString (removing the import)
  2. setup sobek runtime with function and logger to see output
  3. view output and all fields are accounted for

example:

func TestThatMagic(t *testing.T) {
	vm := sobek.New()
	vm.SetFieldNameMapper(sobek.TagFieldNameMapper("json", true))
	vm.Set("magic", magic)
	vm.Set("log", log)

	_, err := vm.RunString(SCRIPT)
	if err != nil {
		panic(err)
	}
}

Expected behaviour

we should be able to unmarshal objects properly like vanilla sobek can.

Actual behaviour

struct does not contain the field data:

 {
   "order": {
     "id": "",
     "sku": {
       "id": ""
     }
   },
   "superStatus": "[object Object]", // this is ok
   "countryPopStar": {
     "id": ""
   },
   "normal": "string" // this works
 }
@scottkgaran scottkgaran changed the title passing a complex object from js to go function does not marshal all fields passing a complex object from js to go function does not unmarshal all fields Oct 30, 2024
@olegbespalov
Copy link
Contributor

Hi @scottkgaran

Sorry for the long reply 😢

I guess the issue here is the and if you tried to use this in k6 as an extension.

vm.SetFieldNameMapper(sobek.TagFieldNameMapper("json", true))

We allow only js tag for mapping the tags.

So, if you can, your structs should look like:

type SKU struct {
	Id string `js:"id"`
}

Here is the working example:

package magic

import (
	"fmt"
	"testing"

	"github.com/grafana/sobek"
)

const SCRIPT = `
let vars = {id: '123', details: {name: 'John Doe', note: 'lorem ipsum', more_details: {age: 30}}};
magic(vars);
`

type Order struct {
	Id      string `js:"id"`
	Details struct {
		Name        string `js:"name"`
		Note        string `js:"note"`
		MoreDetails struct {
			Age int `js:"age"`
		} `js:"more_details"`
	} `js:"details"`
}

type module struct {
	rt *sobek.Runtime
}

func (m *module) Magic(v sobek.Value) string {
	xx := &Order{}

	if err := m.rt.ExportTo(v, xx); err != nil {
		panic(err)
	}

	return fmt.Sprintf("Id: %s, Name: %s, Note: %s", xx.Id, xx.Details.Name, xx.Details.Note)
}

func TestThatMagic(t *testing.T) {
	vm := sobek.New()
        // this is what used in k6
	vm.SetFieldNameMapper(sobek.TagFieldNameMapper("js", true))

	module := &module{rt: vm}
	vm.Set("magic", module.Magic)

	result, err := vm.RunString(SCRIPT)
	if err != nil {
		panic(err)
	}

	str := result.ToString().String()

	expected := "Id: 123, Name: John Doe, Note: lorem ipsum"
	if expected != str {
		t.Errorf("Expected %q result, got %q", expected, result)
	}
}

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