-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: add a type that can distinguish between unset values and null values #50360
Comments
Please note that you should fill https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing a language change. |
package main
import (
"encoding/json"
"errors"
"fmt"
"unsafe"
)
type JSONString []byte
const _leadOfJSONString = '^'
const _leadOfJSONStringLen = 1
var _emptyJSONString = []byte{'"', '"'}
func (s JSONString) MarshalJSON() ([]byte, error) {
if !s.IsSet() {
b := make([]byte, len(_emptyJSONString))
copy(b, _emptyJSONString)
return b, nil
}
b := make([]byte, 0, len(s)-_leadOfJSONStringLen+len(_emptyJSONString))
b = append(b, _emptyJSONString[0]) //"
b = s.AppentString(b) //aaaaa
b = append(b, _emptyJSONString[1]) //"
return b, nil
}
func (s *JSONString) UnmarshalJSON(b []byte) error {
if len(b) < len(_emptyJSONString) || b[0] != _emptyJSONString[0] || b[len(b)-1] != _emptyJSONString[len(_emptyJSONString)-1] {
return errors.New("missing quotation mark")
}
if len(b) == len(_emptyJSONString) { //empty string
*s = make([]byte, _leadOfJSONStringLen)
(*s)[0] = _leadOfJSONString
return nil
}
b = b[1 : len(b)-1]
bs := *(*string)(unsafe.Pointer(&b))
s.Set(bs)
return nil
}
func (s *JSONString) String() string {
if !s.IsSet() {
return ""
}
return string((*s)[1:])
}
func (s *JSONString) AppentString(buf []byte) []byte {
if !s.IsSet() {
return buf
}
return append(buf, (*s)[1:]...)
}
func (s *JSONString) Set(v string) {
if len(*s) == 0 || len(*s)-len(v) > 128 {
*s = make([]byte, 0, _leadOfJSONStringLen+len(v))
} else {
*s = (*s)[:0]
}
*s = append([]byte{_leadOfJSONString}, v...)
}
func (s *JSONString) IsSet() bool {
return len(*s) > 0 && ((*s)[0] == _leadOfJSONString)
}
func (s *JSONString) Clear() {
*s = nil
}
type StringTestMessage struct {
ID int `json:"id"`
Name JSONString `json:"name,omitempty"`
}
func StringUnmarshalTest() {
{
msg := &StringTestMessage{}
err := json.Unmarshal([]byte(`{"id":0}`), msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%+v\n", msg.Name.IsSet(), msg) //false|&{ID:0 Name:[]}
}
{
msg := &StringTestMessage{}
err := json.Unmarshal([]byte(`{"id":111}`), msg) //false|&{ID:111 Name:[]}
if err != nil {
panic(err)
}
fmt.Printf("%t|%+v\n", msg.Name.IsSet(), msg)
}
{
msg := &StringTestMessage{}
err := json.Unmarshal([]byte(`{"id":111,"name":""}`), msg) //true|&{ID:111 Name:[94]}
if err != nil {
panic(err)
}
fmt.Printf("%t|%+v\n", msg.Name.IsSet(), msg)
}
{
msg := &StringTestMessage{}
err := json.Unmarshal([]byte(`{"id":111,"name":"123"}`), msg) //true|&{ID:111 Name:[94 49 50 51]}
if err != nil {
panic(err)
}
fmt.Printf("%t|%+v\n", msg.Name.IsSet(), msg)
}
}
func StringMarshalTest() {
msg := &StringTestMessage{}
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //false|{"id":0}
msg.ID = 111
b, err = json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //false|{"id":111}
msg.Name.Set("")
b, err = json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //true|{"id":111,"name":""}
msg.Name.Clear()
b, err = json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //false|{"id":111}
msg.Name.Set("")
b, err = json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //true|{"id":111,"name":""}
msg.Name.Clear()
b, err = json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //false|{"id":111}
msg.Name.Set("")
b, err = json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //true|{"id":111,"name":""}
msg.Name.Set("123")
b, err = json.Marshal(msg)
if err != nil {
panic(err)
}
fmt.Printf("%t|%s\n", msg.Name.IsSet(), b) //true|{"id":111,"name":"123"}
} |
what advantages does this have over using a pointer? ( |
This is marked Go 2 but it doesn't seem to be a language change. It seems to be a suggestion for a new type in the encoding/json package. Adjusting accordingly. |
The encoding/json package already permits distinguishing between unset strings and empty strings by using a pointer. Why should we add a different mechanism? Thanks. |
I'm not sure if using pointers is an official recommendation and whether there will be memory loss, I will use json. marshal and unmarshal a lot in my scene。 I haven't understood the existing realization of |
Pointer are almost a solution, but not quite yet. When working with Web APIs I often have to distinguish between a field in JSON being passed A good solution might be to use a Null[type] built similarly to sql.NullString: if the field is passed null, However, we miss one bit for it to work properly: if a field in a struct is a pointer and the JSON for it is |
There was a discussion in #48702 about optional types, and some people suggested just putting them in |
I am now using this method to differentiate between unset and null values。 Is it possible to provide the relevant types in the standard library?
The text was updated successfully, but these errors were encountered: