go开发过度使用map[string]interface{}?

Excessive use of map[string]interface{} in go development?

我的大部分开发经验都来自 PHP 和 Javascript 等动态类型语言。通过在 Golang 中重新创建一些旧的 PHP/Javascript REST APIs,我已经使用 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 步 - 将数据绑定到 SQL 查询

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

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

Scan() 我的查询结果变成了 sqlResult := []map[string]interface{}{} 因为我不关心值类型是空值、字符串、浮点数、整数还是其他什么。所以 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"
    }

我编写了自己的预加载函数,这样我就可以像这样绑定更多信息 EagerLoad("tblAddress", "JOIN ON tblAddress.user_id",&sqlResult),然后用 []map[string]interface{}{} 类型的更多信息填充 sqlResult,这样它看起来像这个:

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 整理并发送 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。这是使用结构标签对 json 值进行验证的非常好的实现。

对于第 4 步

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

现在你已经说过你不关心来自 sql 的结果是否为空。但是,如果您再次这样做,就会使用 struct。您可以使用 sql.NullTypes 将结果扫描到结构中。如果您想在发送响应时 marshaling 数据时省略 json 对象,您还可以为 omitempty 提供 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 开发人员尽可能坚持结构。