JSON 和处理未导出的字段

Posted

技术标签:

【中文标题】JSON 和处理未导出的字段【英文标题】:JSON and dealing with unexported fields 【发布时间】:2012-06-23 00:24:12 【问题描述】:

encoding/json 不包含未导出的字段是否有技术原因?如果不是,并且这是一个任意决定,即使未导出,是否还有一个额外的后门选项(比如“+”)要包括在内?

要求导出客户端代码以获取此功能感觉很不幸,尤其是在小写提供封装或编组结构的决定远晚于它们的设计时。

人们如何处理这个问题?只导出所有内容?

此外,导出字段名称不会使遵循建议的习语变得困难。我认为如果结构 X 具有字段 Y,则不能有访问器方法 Y()。如果您想提供对 Y 的接口访问,则必须为 getter 取一个新名称,并且根据http://golang.org/doc/effective_go.html#Getters,无论如何您都会得到一些不惯用的东西

【问题讨论】:

【参考方案1】:

有技术原因。 json 库无权使用反射查看字段,除非它们被导出。包只能查看自己包中未导出的类型字段

为了解决您的问题,您可以做的是使用导出的字段创建一个未导出的类型。如果传递给它没有问题,Json 将解组为未导出的类型,但它不会出现在 API 文档中。然后,您可以制作一个嵌入未导出类型的导出类型。然后,这个导出的类型需要实现json.Marshalerjson.Unmarshaler 接口的方法。

注意:所有代码都未经测试,甚至可能无法编译。

type jsonData struct 
    Field1 string
    Field2 string


type JsonData struct 
    jsonData


// Implement json.Unmarshaller
func (d *JsonData) UnmarshalJSON(b []byte) error 
    return json.Unmarshal(b, &d.jsonData)


// Getter
func (d *JsonData) Field1() string 
    return d.jsonData.Field1

【讨论】:

为了记录,我不得不使用“json.Unmarshal(b, &d.jsonData)”解组。我是不是做错了什么,还是意料之中? @Derek,谢谢,我更新了我的答案。正如我所说,代码未经测试。我显然也忘记了我的UnmarshalJSON() 方法中的return 语句。我也解决了这个问题。 我有点迟到了,但是......虽然上面的作品,Field1 和 Field2 被导出。您可以在该包之外读取和写入 JsonData(大写 J)的 Field1 和 Field2。因此,虽然这在理论上很酷,但实际上与导出类型和字段并没有什么不同。 @davidjosepha 您是正确的,因为最初显示的答案是尽管有人必须查看您的源代码才能知道存在 Field1Field2,因为 godoc 不会列出未导出的结构jsonData。但是,可以通过在 JsonData 结构中不使用匿名字段并使用未导出的命名字段来解决此问题。这要求通过命名的、未导出的字段访问Field1Field2。我编辑了答案以使用这种方法。【参考方案2】:

斯蒂芬的回答是完整的。顺便说一句,如果您真正想要的是 json 中的小写键,您可以手动指定键名,如下所示:

type Whatever struct 
    SomeField int `json:"some_field"`

以这种方式,编组一个 What 为字段 SomeField 生成键“some_field”(而不是在您的 json 中包含“SomeField”)。

如果您对保留未导出的字段一无所知,您还可以通过定义带有签名MarshalJSON() ([]byte, error) 的方法来实现 json.Marshaler 接口。一种方法是使用结构文字,该结构文字仅具有未导出字段的导出版本,如下所示:

type Whatever struct 
    someField int


func (w Whatever) MarshalJSON() ([]byte, error) 
    return json.Marshal(struct
        SomeField int `json:"some_field"`
    
        SomeField: w.someField,
    )

这可能有点麻烦,所以如果您愿意,也可以使用map[string]interface

func (w Whatever) MarshalJSON() ([]byte, error) 
    return json.Marshal(map[string]interface
        "some_field": w.SomeField,
    )

但是应该注意的是,封送interface 有一些注意事项,并且可以将uint64 封送为浮点数,从而导致精度损失。 (所有代码未经测试)

【讨论】:

从技术上讲,javascript 中的所有数字都是浮点数。 JSON 不是 JavaScript。 JSON 规范仅说明哪些字符是可接受的,而不是哪些数字范围是有效的。像 876234958273645982736459827346598237465923847561203947812435968234659827346 这样的数字在 JSON 中仍然有效,即使它无法被 JavaScript 理解。对于现实世界的示例,Twitter API 将推文 ID 表示为 64 位无符号整数,这在 JavaScript 中无效,但在 JSON 中是有效的。 目前,一切都正确,但名称仍然是 JSON,它在历史上代表“JavaScript Object Notation”。让您想将其重命名为 NJSON(NewJSON 或更确切地说 NotJavaScript)的那些微小的令人惊讶的位之一:)【参考方案3】:

使用界面将是另一种选择。

type Person interface 
    Name() string
    SetName(name string) Person
    Age() int
    SetAge(age int) Person


type person struct 
    Name_ string
    Age_  int


func (p *person) Name() string 
    return p.Name_


func (p *person) SetName(name string) Person 
    p.Name_ = name

    return p


func (p *person) Age() int 
    return p.Age_


func (p *person) SetAge(age int) Person 
    p.Age_ = age

    return p


func NewPerson() Person 
    return &person

由于person struct 是小写字母,您将无法访问其包之外的公共字段。要实例化一个 person 值,您需要提供一个构造函数,该构造函数返回用大写 Person 接口包装的值。

Playground

【讨论】:

以上是关于JSON 和处理未导出的字段的主要内容,如果未能解决你的问题,请参考以下文章

使用“jq”从 JSON 文件导出所需的输出(按正确顺序的字段)

Jayrock:未导出 Web 方法

axios设置responseType===blob导出文件和失败返回json处理

axios设置responseType===blob导出文件和失败返回json处理

axios设置responseType===blob导出文件和失败返回json处理

Room - 模式导出目录未提供给注释处理器,因此我们无法导出模式