TL;DR
只需使用json.Unmarshal。您可以使用您的传输方式轻轻包装它,然后在您的预构建 JSON 字节和来自调用者的 v interface{} 参数上调用 json.Unmarshal(或使用 json.Decoder 实例,使用 d.Decode)。
有点长,举个例子
想想json.Unmarshal 如何发挥自己的魔力。它的第一个参数是 JSON (data []byte),但它的第二个参数是 interface{} 类型:
func Unmarshal(data []byte, v interface{}) error
正如文档继续说的,如果v 真的是只是一个interface{}:
为了将 JSON 解组为接口值,Unmarshal 将其中一项存储在接口值中:
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
但如果v 有一个底层的具体类型,比如type myData struct { ... },那就更棒了。如果v 的底层类型是 interface{},它只会执行上述操作。
它的actual implementation 特别复杂,因为它经过优化可以同时进行去JSON 化和分配到目标对象。但原则上,它主要是对接口值的底层(具体)类型进行大的类型切换。
同时,您在问题中描述的是,您将首先反序列化为通用 JSON(这实际上意味着 interface{} 类型的变量),然后对这个 pre 进行自己的赋值 out - 将 JSON 解码为另一个 interface{} 类型的变量,您自己的解码器的类型签名将是:
func xxxDecoder(/* maybe some args here, */ v interface{}) error {
var predecoded interface{}
// get some json bytes from somewhere into variable `data`
err := json.Unmarshal(data, &predecoded)
// now emulate json.Unmarshal by getting field names and assigning
... this is the hard part ...
}
然后您将通过编写以下代码调用此代码:
type myData struct {
Field1 int `xxx:"Field1"`
Field2 string `xxx:"Field2"`
}
这样您就知道 JSON 对象键“Field1”应该用整数填充您的 Field1 字段,而 JSON 对象键“Field2”应该用字符串填充您的 Field2 字段:
func whatever() {
var x myData
err := xxxDecode(..., &x)
if err != nil { ... handle error ... }
... use x.Field1 and x.Field2 ...
}
但这很愚蠢。你可以写:
type myData struct {
Field1 int `json:"Field1"`
Field2 string `json:"Field2"`
}
(或者甚至省略标签,因为字段的名称是默认的 json 标签),然后这样做:
func xxxDecode(..., v interface{}) error {
... get data bytes as before ...
return json.Unmarshal(data, v)
}
换句话说,只需让json.Unmarshal 完成所有工作,方法是在相关数据结构中提供 json 标签。您仍然可以通过您的特殊传输获得并传输 JSON 数据字节来自 json.Marshal 和 json.Unmarshal。你负责发送和接收。 json.Marshal 和 json.Unmarshal 尽一切努力:你不必碰它!
看看Json.Unmarshal 的工作原理还是很有趣的
跳转到around line 660 of encoding/json/decode.go,您将在其中找到处理JSON“对象”({ 后跟} 或表示键的字符串)的东西,例如:
func (d *decodeState) object(v reflect.Value) error {
有一些机制可以处理极端情况(包括v 可能不可设置和/或可能是应遵循的指针这一事实),然后它确保v 是map[T1]T2或struct,如果它是一个映射,那么它是合适的——T1 和T2 在解码对象中的 "key":value 项时都可以工作。
如果一切顺利,它会从第 720 行开始进入 JSON 键值扫描循环(for {,它将酌情中断或返回)。在此循环的每次行程中,代码首先读取 JSON 键,将 : 和值部分留待以后使用。
如果我们要解码为struct,解码器现在使用结构的字段(名称和json:"..." 标签)来查找我们将用于存储到字段中的reflect.Value。 1 这是subv,通过调用v.Field(i) 找到右边的i,带有一些稍微复杂的goo 来处理嵌入式匿名structs 和指针跟随。不过,其核心只是subv = v.Field(i),其中i 是结构中此键名称的任何字段。所以subv 现在是一个reflect.Value,它代表实际结构实例的值,一旦我们解码了 JSON 键值对的值部分,就应该设置它。
如果我们要解码成map,我们会先将值解码成一个临时值,然后在解码后将它存储到map中。将其与 struct-field 存储共享会很好,但我们需要一个不同的 reflect 函数来存储到地图中:v.SetMapIndex,其中v 是地图的reflect.Value。这就是为什么对于地图,subv 指向一个临时的Elem。
我们现在已经准备好将实际值转换为目标类型,因此我们返回 JSON 字节并使用冒号 : 字符并读取 JSON 值。我们获取值并将其存储到我们的存储位置 (subv)。这是从第 809 行 (if destring {) 开始的代码。实际分配是通过解码器函数(第 908 行的d.literalStore 或第 412 行的d.value)完成的,这些函数实际上在进行存储时解码 JSON 值。请注意,只有 d.literalStore 真正存储了值——d.value 会调用 d.array、d.object 或 d.literalStore,以便在需要时递归地完成工作。
d.literalStore 因此包含许多switch v.Kind()s:它解析null 或true 或false 或整数或字符串或数组,然后确保它可以将结果值存储到@ 987654399@,并根据刚刚解码的内容和实际的v.Kind() 的组合选择如何将该结果值存储到v.Kind()。所以这里有点组合爆炸,但它完成了工作。
如果一切正常,并且我们正在解码为映射,我们现在可能需要按摩临时的类型,找到真正的键,并将转换后的值存储到映射中。这就是第 830 行 (if v.Kind() == reflect.Map {) 到 867 处最后一个右括号的内容。
1要查找字段,我们首先查看encoding/json/encode.go 以查找cachedTypeFields。它是typeFields 的缓存版本。这是找到 json 标记并将其放入切片的位置。结果通过cachedTypeFields 缓存在由struct 类型的反射类型值索引的映射中。所以我们得到的是第一次使用struct 类型时的慢速查找,然后是快速查找,以获取有关如何进行解码的信息片段。此信息片从 json-tag-or-field 名称映射到: field;类型;是否是匿名结构的子字段;等等:在编码方面,我们需要知道正确解码或编码它所需的一切。 (我没有仔细看这段代码。)