JSON和处理未导出字段

huangapple go评论76阅读模式
英文:

JSON and dealing with unexported fields

问题

没有技术原因导致未导出的字段不包含在encoding/json中吗?如果没有,并且这是一个任意的决定,是否可以有一个额外的后门选项(比如说'+')来包含未导出的字段?

要求客户端代码导出以获得此功能似乎不太理想,特别是如果小写字母提供了封装性,或者对结构体进行编组的决定比设计它们的时间晚得多。

人们是如何处理这个问题的?只是导出所有字段吗?

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

英文:

Is there a technical reason why unexported fields are not included by encoding/json? If not and it is an arbitrary decision could there be an additional back door option (say '+') to include even though unexported?

Requiring client code to export to get this functionality feels unfortunate, especially if lower case is providing encapsulation or the decision to marshal structures comes much later than design of them.

How are people dealing with this? Just export everything?

Also, doesn't exporting field names make it difficult to follow suggested idioms. I think if a struct X has field Y, you can not have an accessor method Y(). If you want to provide interface access to Y you have to come up with a new name for the getter and no matter what you'll get something un-idiomatic according to http://golang.org/doc/effective_go.html#Getters

答案1

得分: 121

有一个技术原因。json库没有权限使用反射来查看未导出的字段。一个包只能查看其自身包中的未导出字段。

为了解决你的问题,你可以创建一个未导出的类型,其中包含导出的字段。如果将该类型传递给json解码器,它将能够成功解码,但是在API文档中不会显示。然后,你可以创建一个导出的类型,嵌入该未导出的类型。这个导出的类型需要实现json.Marshalerjson.Unmarshaler接口的方法。

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

type jsonData struct {
    Field1 string
    Field2 string
}

type JsonData struct {
    jsonData
}

// 实现json解码器
func (d *JsonData) UnmarshalJSON(b []byte) error {
    return json.Unmarshal(b, &d.jsonData)
}

// Getter
func (d *JsonData) Field1() string {
    return d.jsonData.Field1
}
英文:

There is a technical reason. The json library does not have the power to view fields using reflect unless they are exported. A package can only view the unexported fields of types within its own package

In order to deal with your problem, what you can do is make an unexported type with exported fields. Json will unmarshal into an unexported type if passed to it without a problem but it would not show up in the API docs. You can then make an exported type that embeds the unexported type. This exported type would then need methods to implement the json.Marshaler and json.Unmarshaler interfaces.

Note: all code is untested and may not even compile.

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
}

答案2

得分: 73

Stephen的回答很完整。另外,如果你只想要json中的小写键,你可以手动指定键名,如下所示:

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

这样,将Whatever编组为json时,字段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编组为浮点数,导致精度损失。(所有代码未经测试)

英文:

Stephen's answer is complete. As an aside, if all you really want is lowercase keys in your json, you can manually specify the key name as follows:

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

In that way, marshaling a Whatever produces the key "some_field" for the field SomeField (instead of having "SomeField" in your json).

If you're dead-set on keeping unexported fields, you can also implement the json.Marshaler interface by defining a method with the signature MarshalJSON() ([]byte, error). One way to do this is to use a struct literal that simply has exported versions of the unexported fields, like this:

type Whatever struct {
    someField int
}

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

That can be a bit cumbersome, so you can also use a map[string]interface{} if you prefer:

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

However it should be noted that marshaling interface{} has some caveats and can do things like marshal uint64 to a float, causing a loss of precision. (all code untested)

huangapple
  • 本文由 发表于 2012年6月21日 03:29:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/11126793.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定