这是使用 Golang 忽略来自 PUT/PATCH 的 JSON 有效负载中不需要的字段的最佳方法



我遇到使用我们 API 的人需要在我的资源中进行部分更新的情况。我知道 HTTP 明确指定这是一个 PATCH 操作,即使我们这边的人习惯于为此发送 PUT 请求,这就是构建遗留代码的方式。

为了举例说明,想象一下以下简单的结构:

type Person struct {
    Name string
    Age int
    Address string
}

在 POST 请求中,我将提供包含所有三个值(名称、年龄、地址)的有效负载,并在我的 Golang 后端相应地验证它们。简单。

但是,在 PUT/PATCH 请求中,我们知道,例如,name永远不会改变。但是假设我想更改age,那么我只需发送包含新age的 JSON 有效负载:

PUT /person/1 {age:30}

现在说我真正的问题:在我们的 API 的使用者发送包含 name 字段的 JSON 有效负载时,防止有意或无意修改name的最佳实践是什么?

例:

PUT /person/1 {name:"New Name", age:35} 

我想到的可能的解决方案,但我实际上不喜欢它们,是:

  1. 在我的validator方法上,我要么强行删除不需要的字段name,要么回复一条错误消息,指出不允许name

  2. 例如,创建一个DTO对象/结构,它几乎是我的Person结构的扩展,然后将我的JSON有效负载解组到其中

    type PersonPut struct { Age int Address string }

在我看来,这将添加不必要的额外代码和逻辑来抽象问题,但是我没有看到任何其他优雅的解决方案。

老实说,我不喜欢这两种方法,我想知道你们是否面临同样的问题以及如何解决它。

谢谢!

你带来的第一个解决方案是一个很好的解决方案。一些众所周知的框架用于实现类似的逻辑。

例如,最新的 Rails 版本带有一个内置解决方案,以防止用户在请求中添加额外的数据,从而导致服务器更新数据库中的错误字段。它是ActionController::Parameters类实现的一种白名单。

假设我们有一个控制器类作为波纹管。出于此说明的目的,它包含两个update操作。但是你不会在实际代码中看到它。

class PeopleController < ActionController::Base
  # 1st version - Unsafe, it will rise an exception. Don't do it
  def update
    person = current_account.people.find(params[:id])
    person.update!(params[:person])
    redirect_to person
  end
  # 2nd version - Updates only permitted parameters
  def update
    person = current_account.people.find(params[:id])
    person.update!(person_params) # call to person_params method
    redirect_to person
  end

  private
  def person_params
    params.require(:person).permit(:name, :age)
  end
end

由于第二个版本仅允许允许的值,因此它将阻止用户更改有效负载并发送包含新密码值的 JSON:

{ name: "acme", age: 25, password: 'account-hacked' }

有关更多详细信息,请参阅 Rails 文档:动作控制器概述 和 动作控制器::P

如果无法写入名称,则为任何更新请求提供该名称都是无效的。如果名称存在,我会拒绝该请求。如果我想更宽容,我可能会考虑仅在名称与当前名称不同时才拒绝请求。

我不会默默地忽略一个与现在的名字不同的名字。

这可以通过先将 JSON 正文解码为map[string]json.RawMessage来解决。json.RawMessage类型对于延迟实际解码很有用。之后,可以在map[string]json.RawMessage图上应用白名单,忽略不需要的属性,只解码我们想要保留的属性的json.RawMessage

使用

reflect包可以将列入白名单的 JSON 正文解码为结构的过程自动化;可以在此处找到示例实现。

我不

精通 Golang,但我相信一个好的策略是将您的name字段转换为只读字段。

例如,在像Java/.NET/C++这样严格的面向对象语言中,你可以只提供一个Getter,而不能提供一个Setter。

也许 Golang 有一些访问器配置,就像 Ruby 一样......

如果它是只读的,那么它不应该费心接收备用值,它应该忽略它。但同样,不确定 Golang 是否支持它。

我认为干净的方法是将此逻辑放在 PATCH 处理程序中。应该有一些逻辑只会更新您想要的字段。如果解压缩到map[string]string中并仅循环访问要更新的字段,则更容易。此外,您可以将 json 解码为映射,删除所有不想更新的字段,在 json 中重新编码,然后解码为结构。

相关内容

最新更新