通过部分 json 更新 REST API

Posted

技术标签:

【中文标题】通过部分 json 更新 REST API【英文标题】:REST API Update by partial json 【发布时间】:2019-07-13 16:50:19 【问题描述】:

我们正在使用 Golang 实现一个包含 CRUD 的 REST API,在更新服务中,客户端可以发送包含更改字段的部分 JSON,我们需要处理这些更改来更新实体。

从逻辑上讲,我们需要通过 Id 从 DB 获取实体到 struct,然后将 payload json 解组到另一个 struct 并更新实体。

但是,如果有效载荷 json 不完整,例如我有 struct

type Customer struct 
    Id      int64 `json:"id"`
    Name    string `json:"name"`
    Age     int `json:"age"`

JSON 请求看起来像


  "Name": "Updated name"

然后应该用新名称更新客户。

这是个简单的例子,实际上它可以是一个嵌套的struct和嵌套的json,我们如何用golang,或者Java,.NET等其他语言来处理这种情况

【问题讨论】:

您目前如何将 json 解组为 Go 结构? 你可以使用 json.RawMessage 参考这个答案:***.com/questions/11066946/… 如果您在已经填充的结构之上解组 JSON,则只有 JSON 中的字段会在结构中被修改,这非常容易 - 从数据库加载记录,在其上解组 JSON ,然后将其写回数据库。您能否展示您尝试过的方法以及遇到的问题? 请注意,通过RFC 7231,您要么需要使用PATCH 执行部分更新,要么通过PUT 更新与实际资源部分重叠的资源。对于实际资源,这具有部分更新的效果,尽管PUT 的语义仍然存在:用请求中提供的有效负载替换目标资源的当前有效负载。其他任何内容都违反了 HTTP 协议。此外,修补程序应向服务器发送有关如何修改资源以最终处于所需状态的说明。 您可能想做的最接近的事情是PATCHing 媒体类型为application/merge-patch+json 的资源,如RFC 7396 中所述,并且仅适用于此类媒体类型。我仍然建议使用application/json-patch+json 中指定的RFC 6902 虽然 【参考方案1】:

如果更新请求使用相同的 Customer 结构,则结构字段可以是用于区分零值和未在 JSON 中设置的值的指针。 现在您需要做的就是将现有结构合并到更新的Consumer 结构中。 为此,您可以在 Go 中使用 https://github.com/imdario/mergo 库。

package main

import (
    "fmt"
    "github.com/imdario/mergo"
    "encoding/json"
    "os"
)

type Address struct 
    City string `json:"city"`


type Customer struct 
    Id      int64 `json:"id"`
    Name    string `json:"name"`
    Age     int `json:"age"`
    Address *Address `json:"address"`



func main() 
    old1 := &CustomerId:1, Name:"alpha", Age:5, Address:&AddressCity:"Delhi"

    b := []byte(`"name": "beta"`) //no address, age specified picks from old
    up1 := new(Customer)
    json.Unmarshal(b, up1)
    if err := mergo.Merge(up1, old1); err != nil 
        fmt.Printf("err in 1st merge: %v\n", err)
        os.Exit(1)
    
    m1, _ := json.Marshal(up1)
    fmt.Printf("merged to: %v\n", string(m1))

    old2 := &CustomerId:1, Name:"alpha", Age:5, Address:&AddressCity:"Delhi"
    b2 := []byte(` "address": "city": "mumbai"`) //address specified
    up2 := new(Customer)
    json.Unmarshal(b2, up2)
    if err := mergo.Merge(up2, old2); err != nil 
        fmt.Printf("err in 1st merge: %v\n", err)
        os.Exit(1)
    
    m2, _ := json.Marshal(up2)
    fmt.Printf("merged to: %v\n", string(m2))

【讨论】:

感谢@Saurav Prakash,但是如果我想将 Age 的值更新为 0,那么这个框架是不可能的【参考方案2】:

从您的 cmets 看来,您遇到了很多 go 用户遇到的零值问题,即如何判断输入数据是否通过了合法值 - 或者该值是否被默认遗漏归零。

解决这个问题的唯一方法是使用指针。因此,在您的示例中,将您的数据结构更改为:

type Customer struct 
    Id   *int64  `json:"id"`
    Name *string `json:"name"`
    Age  *int    `json:"age"`

然后在解组后,任何未初始化的字段都将具有 nil 值,例如

var c Customer

err := json.Unmarshal(jsonData, &c)
if err != nil 
    panic(err)


if c.Id != nil 
    log.Println("TODO: added SQL update parms for c.Id:", *c.Id)

if c.Name != nil 
    log.Println("TODO: added SQL update params for c.Name:", *c.Name)

if c.Age != nil 
    log.Println("TODO: added SQL update parms for c.Age:", *c.Age)

注意:必须注意确保不会意外引用任何会触发即时 panic 的 nil 指针。

工作playground example。

【讨论】:

以上是关于通过部分 json 更新 REST API的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 JSON API 实现复杂的条件批量部分更新?

api rest 调用更新 django 模型时出现错误 415

将 jquery 与 django rest api 放在一起

如何通过 REST API 更新 jenkins 凭据?

通过部分更新在 REST 中实现 PATCH 方法的官方方法

如何通过执行操作电子邮件通过 keycloak admin rest api 更新密码