Skip to content

proposal: spec: option types #7054

Closed
Closed
@lukescott

Description

@lukescott
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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions