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

proposal: encoding/json: ability to unmarshal primitives as string #38516

Closed
nerg4l opened this issue Apr 18, 2020 · 3 comments
Closed

proposal: encoding/json: ability to unmarshal primitives as string #38516

nerg4l opened this issue Apr 18, 2020 · 3 comments
Labels
Milestone

Comments

@nerg4l
Copy link

@nerg4l nerg4l commented Apr 18, 2020

What version of Go are you using (go version)?

$ go version
go version go1.14.2 windows/amd64

What do you want?

At the moment we can unmarshal primitives from explicit types and strings if we use json:",string" for the value. My proposal is somewhat the oposit. I want to be able to create a string from any primitives. Only primitives should apply, object and array types should return json.UnmarshalTypeError.

Example

{"val":{}}       // json.UnmarshalTypeError
{"val":[]}       // json.UnmarshalTypeError
{"val":null}     // val: "null" as string
{"val":true}     // val: "true" as string
{"val":false}    // val: "false" as string
{"val":42}       // val: "42" as string
{"val":4.2}      // val: "4.2" as string
{"val":"42"}     // val: "42" as string
{"val":"asd\"a"} // val: `asd"a` as string

Option 1 (preferred by me)

Explicit list of the acceptable types (and null).

type s struct {
	Val string `json:"val,string,number,bool,null"`
}

Option 2

json:",string" on a string type tells that all primitives should be converted to a string.

type s struct {
	Val string `json:"val,string"`
}

Why do you want this?

I have an endpoint where one of the field supposed to be a string. However, some clients uses dynamically typed languages to create the JSON and this causes an issue where the field which supposed to be a string becomes a number instead. At the moment a custom string type is used which implements json.Unmarshaler.

Example

type String string

func (s String) String() string {
	return string(s)
}

func (s *String) UnmarshalJSON(item []byte) error {
	switch item[0] {
	case 'n', 't', 'f', '{', '[': // null, true, false, object, array
		return &json.UnmarshalTypeError{
			Value: "number or string",
		}
	case '"': // string
		var ss string
		if err := json.Unmarshal(item, &ss); err != nil {
			return err
		}
		*s = String(ss)
	default: // number
		*s = String(item)
	}
	return nil
}
@ianlancetaylor ianlancetaylor changed the title propsal: json/encode: ability to unmarshal primitives as string proposal: json/encode: ability to unmarshal primitives as string Apr 18, 2020
@gopherbot gopherbot added this to the Proposal milestone Apr 18, 2020
@gopherbot gopherbot added the Proposal label Apr 18, 2020
@ianlancetaylor ianlancetaylor changed the title proposal: json/encode: ability to unmarshal primitives as string proposal: encoding/json: ability to unmarshal primitives as string Apr 18, 2020
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 18, 2020

It seems like there is a way to do this, and it's not going to be a common request. We generally resist adding new knobs to complex packages like encoding/json, because every new knob makes the package harder to use, harder to test, and harder to fix. Is there a compelling reason for us to add this to the package? Thanks.

@nerg4l
Copy link
Author

@nerg4l nerg4l commented Apr 18, 2020

The documentation for json.Marshal says the following:

The "string" option signals that a field is stored as JSON inside a
JSON-encoded string. It applies only to fields of string, floating point,
integer, or boolean types. This extra level of encoding is sometimes used
when communicating with JavaScript programs:

Int64String int64 `json:",string"`

With the use of `json:",string"` we can force a primitive type to be encoded as string. There was already an option to do this by implementing json.Marshaler. If there is an option to encode primitives as a string without implementing json.Marshaler wouldn't it make sense to decode primitives as a string without implementing json.Unmarshaler? This could be useful "when communicating with JavaScript programs" since JavaScript is dynamically typed and cannot guarantee the type.

I do not have any other compelling reason. Feel free to close this issue if you disagree. As you mentioned encoding/json is already complex and I can understand if it doesn't need any additional complexity.

@mvdan
Copy link
Member

@mvdan mvdan commented Apr 19, 2020

I share @ianlancetaylor's thoughts. The options should only cover common use cases, and this seems niche enough that writing twenty lines of extra code seems fine. The code you shared above also seems pretty clean, to be honest. You can also make it behave any way you want, without having to worry about adding complex options to encoding/json.

Really, this is exactly why the marshaler/unmarshaler interfaces were designed. They might be a bit cumbersome to use, but they are really powerful when it comes to custom encoding/decoding behavior.

@nerg4l nerg4l closed this Apr 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.