Skip to content

Breaking Change on v4.12.0 When Binding to map[string]interface{} -> the field become list #2652

Closed

Description

@nicandcranny

Issue Description

Breaking change on v4.12.0:

The c.Bind is now mapping query params & request body to list of string.

Example:

POST /test?query=param

request body

{"field1": "somevalue"}

The code

request := make(map[string]interface{})
if err := c.Bind(&request); err != nil {
	return err
}

fmt.Printf("%#v", request)

Previous behaviour (pre v4.12.0):

Result: map[string]interface {}{"query":"param", "field1":"somevalue"}

Current behaviour (v4.12.0):

Result: map[string]interface {}{"query":[]string{"param"}, "field1":[]string{"somevalue"}}

Checklist

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

Expected behaviour

The behaviour from the code above should keep on returning non-breaking change and still return this on the newest version:

Result: map[string]interface {}{"query":"param", "field1":"somevalue"}

Actual behaviour

In v4.12.0, the result from the code above returned different result:

Result: map[string]interface {}{"query":[]string{"param"}, "field1":[]string{"somevalue"}}

Steps to reproduce

Working code to debug

package main

func main() {
}

Version/commit

v4.12.0

Activity

  1. changed the title [-]Breaking Change on v4.12.0 When Binding to map[string]interface{}[/-] [+]Breaking Change on v4.12.0 When Binding to map[string]interface{} -> the field become list[/+] on Jun 27, 2024
  2. thesaltree commented on Jul 9, 2024

    @thesaltree
    Contributor

    @slzhffktm I've submitted a pull request (#2656) that addresses this issue by maintaining backwards compatibility for map[string]interface{} binding while supporting the new functionality.

    The PR modifies the bindData function to:

    • Store single values as strings
    • Store multiple values as []string

    However, if the maintainers decide not to merge this change, here's an alternative solution you can use in your code to achieve the same result:

    func ConvertToSingleValues(input map[string]interface{}) map[string]interface{} {
        for key, value := range input {
            if strSlice, ok := value.([]string); ok {
                if len(strSlice) == 1 {
                    input[key] = strSlice[0]
                }
            }
        }
        return input
    }
    
    func ExampleFunction(c echo.Context) error {
        request := make(map[string]interface{})
        if err := c.Bind(&request); err != nil {
            return err
        }
        request := ConvertToSingleValues(request)
        return c.JSON(http.StatusOK, request)
    }

    You can use this helper function with new version without breaking code regardless of whether the PR is merged. It processes the map[string]interface{} returned by c.Bind() and converts any []string values to single strings if they contain only one element.

  3. nicandcranny commented on Sep 9, 2024

    @nicandcranny
    Author

    Hello @thesaltree ! Seems like your PR is merged, thank you for your contribution!!

  4. arigon commented on Nov 7, 2024

    @arigon

    However, if the maintainers decide not to merge this change, here's an alternative solution you can use in your code to achieve the same result:

    func ConvertToSingleValues(input map[string]interface{}) map[string]interface{} {
        for key, value := range input {
            if strSlice, ok := value.([]string); ok {
                if len(strSlice) == 1 {
                    input[key] = strSlice[0]
                }
            }
        }
        return input
    }

    Your return header is not necessary because map types are reference types:

    func ConvertToSingleValues(input map[string]interface{}) {
    	for key, value := range input {
    		if strSlice, ok := value.([]string); ok {
    			if len(strSlice) == 1 {
    				input[key] = strSlice[0]
    			}
    		}
    	}
    }

    I stumbled over this issue because I have the same problem after updating to 4.12.
    I'm wondering when this fix will be released, so until now I'm relying on this helper function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions