JSON и работа с неэкспортированными полями
Есть ли техническая причина, по которой неэкспортированные поля не включены в кодировку /json? Если нет, и это произвольное решение, может ли быть добавлена дополнительная опция задней двери (скажем, "+"), даже если она не экспортируется?
Требование клиентского кода к экспорту для получения этой функциональности вызывает сожаление, особенно если нижний регистр обеспечивает инкапсуляцию или решение о маршалинге структур приходит гораздо позже, чем их разработка.
Как люди справляются с этим? Просто экспортировать все?
Кроме того, экспорт имен полей не мешает следовать предложенным идиомам. Я думаю, что если структура X имеет поле Y, вы не можете иметь метод доступа Y(). Если вы хотите предоставить интерфейсный доступ к Y, вы должны придумать новое имя для получателя, и неважно, что вы получите что-то не идиоматическое в соответствии с http://golang.org/doc/effective_go.html
2 ответа
Есть техническая причина. Библиотека json не может просматривать поля, используя отражение, если они не экспортированы. Пакет может только просматривать неэкспортированные поля типов внутри своего пакета
Для решения вашей проблемы вы можете создать неэкспортированный тип с экспортированными полями. Json будет распакован в неэкспортированный тип, если он будет передан ему без проблем, но он не будет отображаться в документации API. Затем вы можете создать экспортируемый тип, который встраивает неэкспортированный тип. Этот экспортированный тип тогда должен был бы использовать методы для реализации json.Marshaler
а также json.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, вы можете вручную указать имя ключа следующим образом:
type Whatever struct {
SomeField int `json:"some_field"`
}
Таким образом, маршалинг объекта Whither создает ключ "some_field" для поля SomeField (вместо "SomeField" в вашем json).
Если вы не можете сохранять неэкспортированные поля, вы также можете реализовать интерфейс json.Marshaler, определив метод с сигнатурой. MarshalJSON() ([]byte, error)
, Один из способов сделать это - использовать структурный литерал, который просто имеет экспортированные версии неэкспортированных полей, например:
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
на поплавок, что приводит к потере точности. (весь код не проверен)
Другой вариант - использование интерфейса.
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
интерфейс.