golang-使用http补丁语义的其余更新请求

type A struct {
    Id int64
    Email sql.NullString
    Phone sql.NullString
}

Assume I have one record in the database

A{1, "x@x.com", "1112223333"}

Send an update request via PUT

curl -X PUT -d '{"Email": "y@y.com", "Phone": null}' http://localhost:3000/a/1 

Here is the psuedo algorithm that would work with a full PUT request (i.e. update all fields of the record A - but it will cause difficulties with the PATCH request semantics - delta update)

-- Unmarshal json into empty record

  a := A{}
  json.Unmarshal([]byte(request.body), &a)

-- Load the record from the database

aFromDb = <assume we got record from db> //A{1, "x@x.com", "1112223333"}

-- Compare a and aFromDB

-- Notice the email change and set it on aFromDb - ok

-- Notice the phone number change -- but wait! Was it set to NULL in the JSON explicitly or was it not even included in the JSON? i.e. was the json request - {"Email": "y@y.com", "Phone": null} or was it {"Email": "y@y.com"}?

How can we tell by just looking at the unmarshaled json into the struct a?

Is there another method to do the update via rest (with patch semantics)? I am looking for a generic way to do it (not tied to a particular struct).

Try adding this tags to the struct:

type A struct {
    Id int64 `json:"Id,omitempty"`
    Email sql.NullString `json:"Email,omitempty"`
    Phone sql.NullString `json:"Phone,omitempty"`
}

In this way if you are serializing and the field is empty then the json will not contain the field.

When deserializing though the field will have a either a value or it will have the default value for the type (Nil for the pointer or empty string for strings).

I created a separate datatype for this purpose. This example is for an int64 (actually string-encoded int64), but you can easily change it to a string as well. The idea behind it is, that the UnmarshalJSON method will only be called if the value is present in the JSON. The type will implement the Marshaler and the Unmarshaler.

// Used as a JSON type
//
// The Set flag indicates whether an unmarshaling actually happened on the type
type RequiredInt64 struct {
    Set   bool
    Int64 int64
}

func (r RequiredInt64) MarshalJSON() ([]byte, error) {
    lit := strconv.FormatInt(r.Int64, 10)
    return json.Marshal(lit)
}

func (r *RequiredInt64) UnmarshalJSON(raw []byte) error {
    var lit string
    var err error
    if err = json.Unmarshal(raw, &lit); err != nil {
        return err
    }
    r.Int64, err = strconv.ParseInt(lit, 10, 64)
    if err != nil {
        return err
    }
    r.Set = true
    return nil
}

So, if Set is false, you know that the value was not present in the JSON.

You could potentially write your own marshalling/uinmarshalling of your struct and react to the raw response within, although it might be non-obvious witgh inspection what that those functions are manipulating.

Alternatively, you could not omitempty within your fields and force null populated fields.

Or, maybe leveraging a different flavor of patching, perhaps http://jsonpatch.com/, which is more explicit in the nature of your modifications. This would require the client to be more understanding of the state of changes than say for a put.