To be fair, in C++ you could use std::optional or something else that's type-safe.
Go has its own philosophy about zero values which is controversial, but at least with pointers you do get to express optional values. And it's not like you can't work around the ergonomic awkwardness that arises:
type Post struct {
Title *string
}
func (p *Post) GetTitle() (string, bool) {
if p.Title == nil {
return "", false
}
return *p.Title, true
}
func (p *Post) SetTitle(s string) {
p.Title = &s
}
Hardly elegant, but this is Go.
Go also run into the same issue when encoding and decoding JSON. Most languages do distinguish between empty string and a missing string, but not Go, which makes it hard to validate anything. I wrote a code generator for JSON Schema [1] recently, which applies validations while deserializing, and has to resort to a rather low-tech trick to do so:
type Post struct {
Title string `json:"title"`
}
func (v *Post) UnmarshalJSON(b []byte) error {
var raw map[string]interface{}{
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
if _, ok := raw["title"]; !ok {
return errors.New("field title: must be set")
}
type plain Post
var p plain
if err := json.Unmarshal(b, &p); err != nil {
return err
}
*v = Post(p)
return nil
}
(Clearly there are slightly more refined and faster ways to do this, but not that easily without importing third-party code, which I wanted to avoid.)
C and Go are in the minority here. This doesn't come up in languages like Rust, Swift, TypeScript, Nim, Haskell, OCaml, C#, F#, or Java >= 8, all of which have some sort of optional support (algebraic data types or built-in). For example, TypeScript:
so, just for fun, here's a way to do that check without double unmarshalling and allocating maps and such for everything under your struct (also does a check for extra fields, but you could pull that out if you want):
type Post struct {
Title string `json:"title"`
}
func (v *Post) UnmarshalJSON(b []byte) error {
dec := json.NewDecoder(bytes.NewReader(b))
required := map[string]struct{}{
"title": struct{}{},
}
tok, err := dec.Token()
if err != nil {
return err
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return errors.New("Expected object")
}
for {
tok, err := dec.Token()
if err != nil {
return err
}
if d, ok := tok.(json.Delim); ok && d == '}' {
break
}
switch tok {
case "title":
delete(required, "title")
err := dec.Decode(&v.Title)
if err != nil {
return err
}
default:
(*v) = Post{}
return errors.New(fmt.Sprintf("Unexpected field %s", tok))
}
}
if len(required) > 0 {
(*v) = Post{}
return errors.New(fmt.Sprintf("Missing %v required fields", len(required)))
}
return nil
}
Thanks, that's neat! The challenge here is that I also need to validate values (e.g. support minimum/maximum), not just within structs, but also standalone values, which means an UnmarshalJSON directly on the type. I might end up doing something like your example, though. (Rejecting extra fields is on my list!)
Go has its own philosophy about zero values which is controversial, but at least with pointers you do get to express optional values. And it's not like you can't work around the ergonomic awkwardness that arises:
Hardly elegant, but this is Go.Go also run into the same issue when encoding and decoding JSON. Most languages do distinguish between empty string and a missing string, but not Go, which makes it hard to validate anything. I wrote a code generator for JSON Schema [1] recently, which applies validations while deserializing, and has to resort to a rather low-tech trick to do so:
(Clearly there are slightly more refined and faster ways to do this, but not that easily without importing third-party code, which I wanted to avoid.)C and Go are in the minority here. This doesn't come up in languages like Rust, Swift, TypeScript, Nim, Haskell, OCaml, C#, F#, or Java >= 8, all of which have some sort of optional support (algebraic data types or built-in). For example, TypeScript:
Or Rust: [1] https://github.com/atombender/go-jsonschema