JSON and dealing with unexported fields - json

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

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
}

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)

Related

Functions as the struct fields or as struct methods

Could anybody help me to clarify in which situations is better to use functions as the struct fields and when as the methods of struct?
A field of function type is not a method, so it's not part of the method set of the struct type. A "true" method declared with the struct type as the receiver will be part of the method set.
That being said, if you want to implement an interface, you have no choice but to define "true" methods.
Methods are "attached" to concrete types and cannot be changed at runtime. A field of function type may be used to "mimic" virtual methods, but as said above, this is not a method. A field of function type may be reassigned at runtime.
Like in this example:
type Foo struct {
Bar func()
}
func main() {
f := Foo{
Bar: func() { fmt.Println("initial") },
}
f.Bar()
f.Bar = func() { fmt.Println("changed") }
f.Bar()
}
Which outputs (try it on the Go Playground):
initial
changed
Fields of function type are often used to store callback functions. Examples from the standard lib are http.Server and http.Transport.

struct embeding, function inputs, polymorphism

I have a parent struct :
type BigPoly struct{
Value []*ring.Poly
}
And two child structs :
type Plaintext BigPoly
type Ciphertext BigPoly
I would like to have functions accepting both Plaintext and Ciphertext. My solution is to use a function of the form :
func Add(a *Ciphertext, b interface{}) (*Ciphertext)
and use a switch-case to decide what to do, but I find it troublesome and it can lead to very complicated cases if the number of inputs grows.
However since Plaintext and Ciphertext have exactly the same structure and internal variables and only differ in their name, is it possible to create a function accepting both Plaintext and Ciphertext in a cleaner way ? I.e. it doesn't care if it is a type Plaintext or Ciphertext, as long as it is a type BigPoly.
Use a non-empty interface:
type Poly interface {
Value() []*ring.Poly
}
Then define your struct as:
type BigPoly struct{
value []*ring.Poly
}
func (p *BigPoly) Value() []*ring.Poly {
return p.value
}
And your consumer as:
func Add(a, b Poly) Poly {
aValue := a.Value()
bValue := b.Value()
// ... do something with aValue and bValue
}

Why does encoding JSON struct members not invoking custom MarshalJSON?

In Golang, I have a struct whose member is a custom int type with constant values. Basically, the custom type is a logical enum.
type Flavor int
const (
Vanilla Flavor = iota
Chocolate
Strawberry
)
func (f *Flavor) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(f.String())), nil
}
The custom type has defined MarshalJSON and UnmarshalJSON functions so when I serialize the custom type to JSON, I expect to get the string of the value in the serialized output, not the int value.
My issue is that if I have a pointer to a containing type, then the containing type marshals using the custom function but if try to marshal with just a struct value, the custom MarshalJSON is not invoked by the JSON package
type Dessert struct {
Flavor Flavor `json:"flavor"`
Count int
}
....
d := Dessert{Strawberry, 13}
b, err = json.Marshal(d) // !! does not invoke members Marshal !!
b, err = json.Marshal(&d) // works as expected
....
produces
{"flavor":2,"Count":13}
{"flavor":"Strawberry","Count":13}
I expected the second output in both case.
Why does passing a struct value not invoke MarshalJSON on the member but it does encode otherwise correct JSON?
see https://play.golang.org/p/mOl1GHhgynf
for full working code
In your code Flavor does not have a method MarshalJSON as you defined the method for *Flavor only.
If you want type Flavor to have the MarshalJSON method you must define it on Flavor not *Flavor.
oh huh. I think you had it Volker and Leon. I had assumed that I needed a pointer receiver for MarshalJSON since UnmarshalJSON definitely needs a pointer receiver. But
func (f Flavor) MarshalJSON() ([]byte, error) {
...
func (f *Flavor) UnmarshalJSON(b []byte) error {
...
and mixing the receivers causes the expected output for both json.Marshal(d) and json.Marshal(&d)

MarshalJSON not called

I'm trying to customize the output of MarshalJSON, using the interface:
func (m *RawMessage) MarshalJSON() ([]byte, error)
I followed that tutorial: http://choly.ca/post/go-json-marshalling/
My purpose is removing replace one of the fields with true/false (if set or not), so I ended up writing that function:
func (u *Edition) MarshalJSON() ([]byte, error) {
var vaultValue bool
vaultValue = true
var onlineValue bool
vaultValue = false
fmt.Println("here")
if u.Vault == nil {
vaultValue = false
}
if u.Online == nil {
onlineValue = false
}
type AliasEdition Edition
return json.Marshal(&struct {
Vault bool `json:"vault,omitempty"`
Online bool `json:"online,omitempty"`
*AliasEdition
}{
Vault: vaultValue,
Online: onlineValue,
AliasEdition: (*Alias)(u),
})
}
The JSON is created from a map with the following instruction:
json.NewEncoder(w).Encode(EditionsMap)
Obviously EditionsMap is a Map of Editions structures:
var EditionsMap map[string]datamodel.Edition
The problem is that the MarshalJSON function apparently is never called.
Probably I'm doing something wrong, but I cannot understand what is the problem, my understanding is that I just need to implement that function in order to get it called.
This is because you declared the Edition.MarshalJSON() method with pointer receiver:
func (u *Edition) MarshalJSON() ([]byte, error)
And you try to marshal non-pointer values (your map contains datamodel.Edition values):
var EditionsMap map[string]datamodel.Edition
// ...
json.NewEncoder(w).Encode(EditionsMap)
Methods with pointer receiver are not part of the method set of the corresponding non-pointer type. The method set of type datamodel.Edition does not contain the method MarshalJSON().
Spec: Method sets:
A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).
Try to marshal pointer values, define your map to contain pointers:
var EditionsMap map[string]*datamodel.Edition
// ...
if err := json.NewEncoder(w).Encode(EditionsMap); err != nil {
panic(err) // HANDLE error somehow, do not omit it like in your example!
}
Values of the pointer type *Edition does have a method MarshalJSON() which will be called properly by the json package. Try a working example of this on the Go Playground.
Another option would be to define the Edition.MarshalJSON() method with value receiver:
func (u Edition) MarshalJSON() ([]byte, error)
And this way it would work no matter if you marshal pointer or non-pointer values, as the methods with value receiver are part of the method set of both the Edition type and the corresponding *Edition pointer type. Try a working example of this variant on the Go Playground.

Converting composed objects to json in Go

I am new to Go and am unsure about how to approach this problem. In OOP terms, I have a base class of Animal and two subclasses of Cat and Dog. I want to specify a ToJson method for Animal which will work for all animals.
My problem is that when I call dog.ToJson() I only get the Dog properties of dog and none of the Animal properties.
How can I make ToJson work as expected (ie with recursion)?
edit: Changed code to reflect suggestions in answer by lbonn, which I could not get to work how I want it to.
edit2: consistency in question following code change
package main
import (
"encoding/json"
"fmt"
)
type Animal struct {
Name string
}
type Cat struct {
CatProperty int64
Animal
}
type Dog struct {
DogProperty int64
Animal
}
func ToJson(i interface{}) []byte {
data,err := json.Marshal(i)
if err != nil {
panic("???")
}
return data
}
func main() {
dog := Dog{}
dog.Name = "rex"
dog.DogProperty = 2
fmt.Println(string(ToJson(dog)))
// Prints {"DogProperty":2}
// I want it to print {"Name":"rex","DogProperty":2}
}
Json encoding of anonymous fields was dropped from go 1. Hopefully it will be back in go 1.1. See https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/jYMHbEv44r4 for more details.
So the best you can get with the standard library right now (with go 1) is http://play.golang.org/p/LncNFeN8ys
You can always use skelterjohn's patch https://github.com/skelterjohn/json/ to support anonymous fields till go 1.1 is released.
Or use tip, installing from source that has this issue fixed. see https://codereview.appspot.com/6460044
Here, the ToJson method applies to the anonymous field Animal of Dog. The call d.ToJson is only a visibility shortcut to d.Animal.ToJson.
GoLang Tutorials: Anonymous fields in struct
Here, I would write a function instead of a method (a simple wrapper around Marshal):
func ToJson(i interface{}) []byte {
data,err := json.Marshal(i)
if err != nil {
panic("???")
}
return data
}
This is not specific to animals or dogs but it doesn't really need to.
More generally, there is no real notion of inheritance in go. The object paradigm used in the language is quite different from mainstream OOP, like in Java or C++. The Go FAQ provides some good clarifications about it.