json - 在 Go 开发中过度使用 map[string]interface{}?

标签 json go struct interface strong-typing

我的大部分开发经验来自动态类型语言,如 PHP 和 Javascript。通过在 Golang 中重新创建一些旧的 PHP/Javascript REST API,我已经使用 Golang 练习了大约一个月。我觉得我大部分时间都没有按照 Golang 的方式做事。或者更一般地说,我不习惯使用强类型语言。我觉得我过度使用 map[string]interface{}以及它们的一部分,用于在数据来自 http 请求或作为 json http 输出发送时将数据装箱。那么我想知道的是,我要描述的内容是否违背了 golang 的开发理念?或者,如果我违反了使用强类型语言进行开发的原则?

目前,我用 Golang 重写的 REST APIs 程序流程中,大约 90% 都可以通过这 5 个步骤来描述。

第 1 步 - 接收数据

我从 http.Request.ParseForm() 收到 http 表单数据如 formvals := map[string][]string .有时我会存储需要解码的序列化 JSON 对象,如 jsonUserInfo := json.Unmarshal(formvals["user_information"][0]) /* gives some complex json object */ .

第 2 步 - 验证数据

我在 formvals 上进行验证在 SQL 查询中使用它之前,确保所有数据值都是我所期望的。我将所有内容都视为字符串,然后使用 Regex 来确定字符串格式和业务逻辑是否有效(例如 IsEmail、IsNumeric、IsFloat、IsCASLCompliant、IsEligibleForVoting、IsLibraryCardExpired 等...)。我已经为这些类型的验证编写了自己的正则表达式和自定义函数

第 3 步 - 将数据绑定(bind)到 SQL 查询

我用的是 golang 的 database/sql.DB获取我的表单并将它们绑定(bind)到我的 Query 和 Exec 函数,就像这样 Query("SELECT * FROM tblUser WHERE user_id = ?, user_birthday > ? ",formvals["user_id"][0], jsonUserInfo["birthday"]) .我从不关心我作为要绑定(bind)的参数提供的数据类型,所以它们可能都是字符串。我相信上面步骤中的验证已经确定它们可以用于 SQL 使用。

第 4 步 - 将 SQL 结果绑定(bind)到 []map[string]interface{}{}

Scan()我的查询结果变成了sqlResult := []map[string]interface{}{}因为我不在乎值类型是 null、strings、float、ints 还是其他类型。所以 sqlResult 的模式可能看起来像:

sqlResult =>
    [0] {
        "user_id":"1"
        "user_name":"Bob Smith"
        "age":"45"
        "weight":"34.22"
    },
    [1] {
        "user_id":"2"
        "user_name":"Jane Do"
        "age":nil
        "weight":"22.22"
    }

我写了自己的eager load函数,这样我就可以绑定(bind)更多这样的信息EagerLoad("tblAddress", "JOIN ON tblAddress.user_id",&sqlResult)然后填充 sqlResult更多信息类型为[]map[string]interface{}{}这样它看起来像这样:
sqlResult =>
    [0] {
        "user_id":"1"
        "user_name":"Bob Smith"
        "age":"45"
        "weight":"34.22"
        "addresses"=>
            [0] {
                "type":"home"
                "address1":"56 Front Street West"
                "postal":"L3L3L3"
                "lat":"34.3422242"
                "lng":"34.5523422"
            }
            [1] {
                "type":"work"
                "address1":"5 Kennedy Avenue"
                "postal":"L3L3L3"
                "lat":"34.3422242"
                "lng":"34.5523422"
            }
    },
    [1] {
        "user_id":"2"
        "user_name":"Jane Do"
        "age":nil
        "weight":"22.22"
        "addresses"=>
            [0] {
                "type":"home"
                "address1":"56 Front Street West"
                "postal":"L3L3L3"
                "lat":"34.3422242"
                "lng":"34.5523422"
            }
    }

第 5 步 - JSON Marshal 并发送 HTTP 响应

然后我做了一个 http.ResponseWriter.Write(json.Marshal(sqlResult))并为我的 REST API 输出数据

最近,我一直在重新阅读带有代码示例的文章,这些示例在我本会使用的地方使用了结构 map[string]interface{} .例如,我想使用其他 golang 开发人员会使用的更标准的方法来重构第 2 步。所以我找到了这个 https://godoc.org/gopkg.in/go-playground/validator.v9 ,除了它的所有例子都是 structs 。我还注意到大多数博客都在谈论 database/sql将他们的 SQL 结果扫描到类型化变量或具有类型化属性的结构中,这与我的第 4 步相反,它只是将所有内容放入 map[string]interface{}
因此,我开始写这个问题。我觉得map[string]interface{}非常有用,因为在大多数情况下,我并不真正关心数据是什么,它让我可以在第 4 步中自由地构建任何数据模式,然后再将其转储为 JSON http 响应。我以尽可能少的代码冗长完成所有这些。但这意味着我的代码还没有准备好利用 Go 的验证工具,而且它似乎不符合 golang 社区的做事方式。

所以我的问题是,其他 golang 开发人员对第 2 步和第 4 步做了什么?尤其是在第 4 步中……Golang 开发人员真的鼓励通过结构和强类型属性指定数据的模式吗?他们是否还指定具有强类型属性的结构以及他们所做的每个急切加载调用?这看起来不是更冗长的代码吗?

最佳答案

这真的取决于需求,就像您所说的那样,您不需要处理来自请求或 sql 结果的 json。然后你可以轻松地解码到 interface{} .并编码来自 sql 结果的 json。

对于 步骤 2

Golang 有一个库,用于验证用于解码 json 的结构体,其中包含字段的标签。

https://github.com/go-playground/validator

type Test struct {
    Field `validate:"max=10,min=1"`
}

// max will be checked then min

你也可以去 godoc 获取 validation library .这是使用 struct 标签验证 json 值的非常好的实现。

对于 第 4 步

大多数情况下,如果我们知道 JSON 的格式和数据,我们就会使用结构。因为它为我们提供了对数据类型和其他功能的更多控制。例如,如果您想清空 JSON 字段,而您的 JSON 中不需要它。您应该将 struct 与 _ 一起使用json 标签。

现在您已经说过您不在乎来自 sql 的结果是否为空。但是如果你再做一次,就会涉及到使用 struct 。您可以使用 sql.NullTypes 将结果扫描到结构中.有了它,你也可以为 omitempty 提供 json 标签如果你想在 marshaling 时省略 json 对象发送响应时的数据。

Struct values encode as JSON objects. Each exported struct field becomes a member of the object, using the field name as the object key, unless the field is omitted for one of the reasons given below.

The encoding of each struct field can be customized by the format string stored under the "json" key in the struct field's tag. The format string gives the name of the field, possibly followed by a comma-separated list of options. The name may be empty in order to specify options without overriding the default field name.

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

As a special case, if the field tag is "-", the field is always omitted. Note that a field with name "-" can still be generated using the tag "-,".



json标签示例
// Field appears in JSON as key "myName".
Field int `json:"myName"`

// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`

// Field is ignored by this package.
Field int `json:"-"`

// Field appears in JSON as key "-".
Field int `json:"-,"`

正如您可以从 Golang 规范中为 json marshal 提供的上述信息进行分析.结构提供了对 json 的如此多的控制。这就是 Golang 开发人员最有可能使用结构体的原因。

正在使用 map[string]interface{}当您的 json 结构不是来自服务器或字段类型时,您应该使用它。大多数 Golang 开发人员都尽可能坚持使用结构。

关于json - 在 Go 开发中过度使用 map[string]interface{}?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51581729/

相关文章:

go - 为 Go 中的结构返回 nil

go - 无法使用远程 api 从 go 连接到谷歌云数据存储

c++ - 使用命名空间时如何在结构本身中定义静态结构成员

javascript - 在 JQuery 中解析 JSON 数组

java - 对于 Jackson,如何安全地共享 ObjectMapper ?有没有不可变的ObjectMapper?

javascript - 根据对象过滤嵌套 JSON

xml - 压缩文件夹最终会出错,具体取决于1个文件

ruby - 在 Ruby 中解析 JSON(如 XPATH)

c - 在 C 中使用 memcpy 访问结构成员

c 在结构中定义不同大小的数组