Skip to content

UnmarshalParam doesn't seem to be called on POST #1405

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

Closed
3 tasks done
kuchlein opened this issue Sep 21, 2019 · 7 comments
Closed
3 tasks done

UnmarshalParam doesn't seem to be called on POST #1405

kuchlein opened this issue Sep 21, 2019 · 7 comments
Labels

Comments

@kuchlein
Copy link

kuchlein commented Sep 21, 2019

Issue Description

When posting a struct that includes a field that implements Echo#BindUnmarshaler, it doesn't seem to call UnmarshalParam for it when calling c.Bind. It does however work on GET with query parameters (there is a test for this already).

Checklist

  • Dependencies installed
  • No typos
  • Searched existing issues and docs

Expected behaviour

I'd expect Echo to call UnmarshalParam when Binding to a struct. The documentation at https://echo.labstack.com/guide/request gives an example for a Timestamp but it doesn't include the struct that was being used. It might be nice to include that in a future version.

Actual behaviour

Given the test below (added to bind_test.go) I get the following error:

--- FAIL: TestPostBindUnmarshalParam (0.00s)
    bind_test.go:288:
        	Error Trace:	bind_test.go:288
        	Error:      	Received unexpected error:
        	            	code=400, message=Unmarshal type error: expected=echo.Timestamp, got=string, field=ts, offset=30, internal=json: cannot unmarshal string into Go struct field .ts of type echo.Timestamp
        	Test:       	TestPostBindUnmarshalParam

Steps to reproduce

Add the test below to bind_test.go and run the test.

Working code to debug

func TestPostBindUnmarshalParam(t *testing.T) {
	e := New()
	body := bytes.NewBufferString(`{ "ts": "1970-12-06T19:09:05Z" }`)
	req := httptest.NewRequest(http.MethodPost, "/", body)
	req.Header.Set(HeaderContentType, MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	result := struct {
		T Timestamp `json:"ts"`
	}{}
	err := c.Bind(&result)
	spew.Dump(result)
	ts := Timestamp(time.Date(1970, 12, 6, 19, 9, 5, 0, time.UTC))
	assert := assert.New(t)
	if assert.NoError(err) {
		assert.Equal(ts, result.T)
	}
}

Version/commit

 ❯ go version
go version go1.13 darwin/amd64

Tested with Echo commit 81a6608.

@stale
Copy link

stale bot commented Nov 21, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Nov 21, 2019
@stale stale bot closed this as completed Nov 28, 2019
@ammmir
Copy link

ammmir commented Dec 9, 2019

I'm also running into this issue. Are there plans to fix it? UnmarshalParam(string) isn't being called as far as I can tell.

@ironytr
Copy link

ironytr commented Feb 8, 2021

Is there any fix? Can't call for post json data.

@aldas
Copy link
Contributor

aldas commented Feb 9, 2021

This is working as intended. UnmarshalParam is not used when doing JSON post binding. JSON payload is marshalled using Golang default json unmarshaller and requires your struct to implement following interface method for json UnmarshalJSON([]byte) error

So for this example to work with Timestamp struct it needs to have following method:

// UnmarshalJSON converts JSON to object
func (t *Timestamp) UnmarshalJSON(b []byte) error {
	ts, err := time.Parse(time.RFC3339, strings.Trim(string(b), `"`))
	*t = Timestamp(ts)
	return err
}

UnmarshalParam only works for Path and Query parameters and is not used for Body

@ironytr
Copy link

ironytr commented Feb 10, 2021

This is working as intended. UnmarshalParam is not used when doing JSON post binding. JSON payload is marshalled using Golang default json unmarshaller and requires your struct to implement following interface method for json UnmarshalJSON([]byte) error

So for this example to work with Timestamp struct it needs to have following method:

// UnmarshalJSON converts JSON to object
func (t *Timestamp) UnmarshalJSON(b []byte) error {
	ts, err := time.Parse(time.RFC3339, strings.Trim(string(b), `"`))
	*t = Timestamp(ts)
	return err
}

UnmarshalParam only works for Path and Query parameters and is not used for Body

Thank you for your Help sir. I have another question not relavent to topic but, how to bind nested form-data with multipart with echo? JSON bind works perfectly. im stuck with form-data (using inertia-js if a request have a file turning form type to multipart, form-data) sample data is:

type ProductUpdateRequest struct {
	ID   uint  `json:"id" form:"id" query:"id" validate:"required"`
	Name string `json:"name" form:"name" query:"name" validate:"required"`
	Description string `json:"description" form:"description" query:"description" validate:"required"`
	DID   uint `json:"did" form:"did" query:"did" validate:"required"`
	ProductMainID string `json:"product_main_id" form:"product_main_id" query:"product_main_id" validate:"required"`
...
	Items []struct{
		Images []*multipart.FileHeader `json:"images" form:"images" query:"images"`
		ID 		uint `json:"id" form:"id" query:"id" validate:""`
		Barcode  string `json:"barcode" form:"barcode" query:"barcode"  validate:"required"`
		...
	} `json:"stockItems" form:"stockItems" query:"stockItems" validate:"required,gt=0,dive,required"`
}
name: Test Ürün!
description: <div>Test Ürün İçerik!</div>
tag: 
category_id: 34
manufacturer_id: 113
stockItems[0][length]: 3131
stockItems[0][list_price]: 3131
stockItems[0][option_ids]: undefined
stockItems[0][options][0][key_name]: Türü
stockItems[0][options][0][key_id]: 10
stockItems[0][quantity]: 3131
stockItems[0][selling_price]: 3131
stockItems[0][show_input]: 1
stockItems[0][special_options]: 
stockItems[0][type]: string
stockItems[0][weight]: 3131
stockItems[0][width]: 3131
filters: 
stores: 
image: 
status: 1
attributes[0][attribute_id]: 1
attributes[0][attribute_name]: test
attributes[0][included]: 0
is_variant: 0

@aldas
Copy link
Contributor

aldas commented Feb 10, 2021

Binding from stockItems[0][length] to slice is not supported by default. See #1644 and #1606

Looking at https://inertiajs.com/forms and https://inertiajs.com/forms#classic-xhr-submits you could transform your form before sending.

These are hacky but you could:
a) transform form to json (without file) and JSON.stringify it and set to single variable in multipart-form which you unmashral by hand in backend to struct/slice (multipart form)

ala

	var payload struct{
		ID int64
	}
	if err := json.Unmarshal([]byte(c.FormValue("form_fields_as_json_string")), &payload); err != nil {

b) if those images are not huge you could transform form to json and images to base64 encoded fields in json.

@ironytr
Copy link

ironytr commented Feb 11, 2021

Binding from stockItems[0][length] to slice is not supported by default. See #1644 and #1606

Looking at https://inertiajs.com/forms and https://inertiajs.com/forms#classic-xhr-submits you could transform your form before sending.

These are hacky but you could:
a) transform form to json (without file) and JSON.stringify it and set to single variable in multipart-form which you unmashral by hand in backend to struct/slice (multipart form)

ala

	var payload struct{
		ID int64
	}
	if err := json.Unmarshal([]byte(c.FormValue("form_fields_as_json_string")), &payload); err != nil {

b) if those images are not huge you could transform form to json and images to base64 encoded fields in json.

Thank you for your kindness and help sir. I am working on it.

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

No branches or pull requests

4 participants