JSON和处理未导出的字段


74

是否存在技术原因,为什么编码/ json不包含未导出的字段?如果不是这样,这是一个任意决定,即使未导出,是否还会有其他后门选项(例如“ +”)包括在内?

要求导出客户端代码以获取此功能感到很不幸,尤其是在小写字母提供封装或封送结构的决定比其设计晚得多的情况下。

人们如何处理这个问题?只是出口一切?

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

Answers:


102

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

为了解决您的问题,您可以做的是使用导出的字段创建未导出的类型。如果将Json毫无问题地传递给未导出的类型,它将解组为未导出的类型,但不会在API文档中显示。然后,您可以创建嵌入未导出类型的导出类型。然后,此导出的类型将需要方法来实现json.Marshalerandjson.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

@Derek,谢谢,我更新了答案。正如我所说,该代码未经测试。我显然也忘记了我的UnmarshalJSON()方法中的return语句。我也解决了这个问题。
Stephen Weinberg 2013年

6
我来晚了一点,但是...虽然上面的作品有效,但Field1和Field2导出。您可以在该程序包之外读写JsonData的Field1和Field2(大写J)。因此,尽管从理论上讲这很酷,但除了导出类型和字段外,它实际上并没有做任何其他事情。
davidjosepha

1
@davidjosepha是正确的,因为最初显示的答案是正确的,尽管由于godoc不会列出未导出的结构,所以某些人必须查看您的源代码才能知道答案Field1Field2存在jsonData。但是,可以通过不在JsonData结构中使用匿名字段并具有未导出的命名字段来解决此问题。这就要求Field1,并Field2通过指定的,不导出字段进行访问。我编辑了答案以使用这种方法。
Darkwing,

64

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

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

这样,封送Whatever会为字段SomeField生成键“ some_field”(而不是在json中具有“ SomeField”)。

如果您对保留未导出的字段不满意,还可以通过定义一个带有signature的方法来实现json.Marshaler接口MarshalJSON() ([]byte, error)。执行此操作的一种方法是使用仅具有未导出字段的导出版本的struct文字,如下所示:

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{}如果您愿意,也可以使用a :

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

但是,应注意的是,封送处理interface{}有一些注意事项,并且可能会像将封uint64送给浮动车辆一样进行操作,从而导致精度损失。(所有代码未经测试)


从技术上讲,Javascript中的所有数字都是浮点数。
泰勒(Tyler)屋檐

23
JSON不是JavaScript。JSON规范仅说明可接受的字符,而不说明有效的数字范围。像876234958273645982736459827346598237465923847561203947812435968234659827346这样的数字在JSON中仍然有效,即使JavaScript无法理解也是如此。举一个真实的例子,Twitter API将tweet ID表示为64位无符号整数,这在JavaScript中无效,但有效JSON。
jorelli

1
当前,所有正确的名称仍然是JSON,它在历史上代表“ JavaScript Object Notation”。那些使您想要将其重命名为NJSON(NewJSON或更确切地说是NotJavaScript)的微小的惊奇位之一:)
quetzalcoatl 2014年

0

使用接口将是另一种选择。

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接口。

操场

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.