|
Go lacks built-in optional types, so SDKs must represent fields
that can be omitted, null, or both in some other way (or forgo
the ability to differentiate).
|
|
|
|
v2 uses a wrapper param.Opt[T] in request types to handle this
disambiguation, with separate response types without wrappers for
idiomatic reads. v3 uses unified types, T/*T for fields, and
handles disambiguation beyond that separately.
In this example, to show all cases,
Name is required+non-nullable,
Age is optional+non-nullable,
Email is required+nullable, and
Bio is optional+nullable.
|
type UserParam struct {
Name string `json:"name"`
Age param.Opt[int] `json:"age,omitzero"`
Email param.Opt[string] `json:"email"`
Bio param.Opt[string] `json:"bio,omitzero"`
paramObj
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
Bio string `json:"bio"`
JSON struct {
Name respjson.Field
Age respjson.Field
Email respjson.Field
Bio respjson.Field
raw string
} `json:"-"`
}
|
type User struct {
Name string `json:"name"`
Age *int `json:"age,omitzero"`
Email *string `json:"email"`
Bio *string `json:"bio,omitzero"`
APIData apidata.APIData `json:"-"`
}
|
|
When setting values on requests, v2 suggests using helpers to
create Opt boxes. In v3 we can use the builtin new to make
pointers.
|
user := api.UserParam{
Name: "Alice",
Age: api.Int(30),
...
}
client.UpdateUser(user)
|
user := api.User{
Name: "Alice",
Age: new(30),
...
}
client.UpdateUser(user)
|
|
To send a required but nullable field as JSON null, v2 requires
param.Null[T](). In v3, a Go nil *T without omitzero
serializes to JSON null, so you could either not set it or
explicitly set it to nil:
|
user := api.UserParam{
...,
Email: param.Null[string](),
}
|
user := api.User{
...,
Email: nil,
}
|
|
For optional+nullable fields like Bio, sending explicit null
is trickier in v3: nil + omitzero would omit the field, not
send null. v2 handles this with param.Null as before. In v3,
use SetField to express the intent:
|
user := api.UserParam{
...,
Bio: param.Null[string](),
}
|
user := api.User{
...,
APIData: apidata.SetField("bio", nil)
}
|
|
On the response side, Name is a plain string in both
versions. For required+nullable Email, v2 uses string
with JSON metadata to detect null; v3 uses *string:
|
fmt.Println(response.Name)
if response.JSON.Email.Valid() {
fmt.Println(response.Email)
}
|
fmt.Println(response.Name)
if response.Email != nil {
fmt.Println(*response.Email)
}
|
|
For optional non-nullable fields like Age, v2’s separate
response type uses a plain int — you need the JSON metadata
struct to know whether the field was present. v3 uses a pointer,
so it’s a simple nil check:
|
if response.JSON.Age.Valid() {
fmt.Println(response.Age)
}
|
if response.Age != nil {
fmt.Println(*response.Age)
}
|
|
For optional+nullable fields like Bio, the zero value is
ambiguous — omitted or explicitly null? Both need metadata
to disambiguate. In v2, Valid() means “present and
non-null”, so Raw() distinguishes null from omitted. v3
uses GetResponseField with map-style ok semantics to handle
the three cases.
|
if response.JSON.Bio.Valid() {
fmt.Println(response.Bio)
} else if response.JSON.Bio.Raw() == "null" {
fmt.Println("null")
} else {
fmt.Println("omitted")
}
|
bio, ok := response.APIData.GetResponseField("bio")
if !ok {
fmt.Println("omitted")
} else if bio == nil {
fmt.Println("null")
} else {
fmt.Println(bio)
}
|