-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Closed
Labels
Milestone
Description
User story:
I'm currently writing a REST API in Go, which uses the encoding/json package. The Update
part of CRUD gets tricky if you want to do partial updates because there is no way to
tell if something was left out of the JSON.
The structure, for example, could look like this:
type Message struct {
Name string
Title string
Message string
Date time.Time
}
With an update I may only want to update Title. To do this I have to change the
structure to this:
type Message struct {
Name *string
Title *string
Message *string
Date *time.Time
}
This allows each value to have 3 states: nil, empty "", or non-empty.
Unfortunately this approach isn't very desirable because the values themselves don't
live in the struct, and this can create garbage that needs to be collected.
With structs that have an optional time.Time, changing it to *time.Time doesn't solve
the problem either. You can't tell the difference between {"Date":null} and {}
(remove expiration date vs don't touch it).
A possible solution to this is by wrapping the types like this:
type Int struct {
int
set bool
}
And this works very well. In fact, database/sql does this.
Full example of Int: http://play.golang.org/p/GbsOucDAPZ
Example of Time: http://play.golang.org/p/OpVVOVBIwc
Unfortunately there is no standardization for wrappers like this. I can use
sql.NullInt64 and keep my data store, model, and controller layers separate. Since my
data store needs to be interchangeable, what if I'm not using SQL?
A standard package to hold these wrapper types would be desirable. And they could
include methods like MarshalJSON and UnmarshalJSON. But what happens when a custom
package needs methods such as these?
Solutions:
Option 1 - A standard package for wrapper types could be created. Either there needs to
be a way to attach methods to a type declared in one package from another, or the
encoding packages need to be aware of these types just as they are the primitives.
Pros -- If the packages are aware of these types (and don't use Marshaler/Unmarshaler
methods) only an addition pkg written in Go needs to be added.
Cons -- This only works well for primitive types. Custom types such a time.Time rely on
Marshaler/Unmarshaler methods. These method are difficult to add for custom encoders /
database packages, etc.. (packages that use reflect).
Option 2 - A type extension, which could be expressed something like:
type Message struct {
Name ?string
Title ?string
Message ?string
Date ?time.Time
}
Internally it would be the same as the wrappers and it would work with a built-int
isset() method. Otherwise the interaction would be identical to the regular types, so
you could do something like msg.Name = "Luke" instead of something like
msg.Name.Set("Luke"). This would also be true with the reflect package, with
the addition of Value.IsSet.
This should be kept distinct from pointers. unset(msg.Name) instead of msg.Name = nil.
An unset() method would set a value to it's zero value and reset the internal bool.
Pros -- Use these types like normal types. Marshal/Unmarshal method would be inherited
from the types themselves.
Cons -- The spec needs to change to allow support for this. I cannot speak for the
challenges in doing this.ggtools, xibz, bpytlik, evillemez, nathanjackson and 25 more