21

The problem I'm trying to solve is that I have a model of a community that looks like this

type Community struct {
    Name string
    Description string
    Sources []Source
    Popularity int
    FavoriteCount int
    Moderators []string
    Children []Community
    Tracks []Track
}

Communities hold a lot of information and there are scenarios when I want to return only part of the description such as if I'm returning a list of trending communities. In this case I'd want to return only

type Community struct {
    Name string
    Description string
    Popularity int
    FavoriteCount int
}

The only way I can think of doing this is to create a new type containing only those fields and write a convenience method that takes a community and returns that type, but essentially creating a new object and copying those fields by value, is there a better way to do this?

I'm aware of the json:"-" syntax, but I'm not sure of how you could do this on a case by case basis as I still need to sometimes return the full object, perhaps a different type that is converted to?

1
  • One possible way is to implement a custom golang.org/pkg/encoding/json/#Marshaler together with an internal configuration field to specify which fields of your struct you want to emit Commented Jul 16, 2014 at 0:24

5 Answers 5

23

[This](http://attilaolah.eu/2014/09/10/json-and-struct-composition-in-go/ ) is a cool approach, which involves creating a sort of Masking struct.

Here's the example in the article:

type User struct {
    Email    string `json:"email"`
    Password string `json:"password"`
    // many more fields…
}

type omit *struct{}

type PublicUser struct {
    *User
    Password omit `json:"password,omitempty"`
}

// when you want to encode your user:
json.Marshal(PublicUser{
    User: user,
})
Sign up to request clarification or add additional context in comments.

Comments

6

I developed a library which can help you in this regard: Sheriff

You can annotate your struct fields with special tags and call Sheriff to transform the given struct into a subset of it. After that you can call json.Marshal() or whatever else you want to marshal into.

Your example would become as simple as:

type Community struct {
    Name          string      `json:"name" groups:"trending,detail"`
    Description   string      `json:"description" groups:"trending,detail"`
    Sources       []Source    `json:"sources" groups:"detail"`
    Popularity    int         `json:"popularity" groups:"trending,detail"`
    FavoriteCount int         `json:"favorite_count" groups:"trending,detail"`
    Moderators    []string    `json:"moderators" groups:"detail"`
    Children      []Community `json:"children" groups:"detail"`
    Tracks        []Track     `json:"tracks" groups:"detail"`
}

communities := []Community{
    // communities
}

o := sheriff.Options{
    Groups: []string{"trending"},
}

d, err := sheriff.Marshal(&o, communities)
if err != nil {
    panic(err)
}

out, _ := json.Marshal(d)

Comments

5

Yep that is the only way as far as I know using the default marshaler. The only other option is if you create your own json.Marshaler .

type Community struct {

}

type CommunityShort Community

func (key *Community) MarshalJSON() ([]byte, os.Error) {
  ...
}

func (key *Community) UnmarshalJSON(data []byte) os.Error {
 ...
}


func (key *CommunityShort) MarshalJSON() ([]byte, os.Error) {
  ...
}

func (key *CommunityShort) UnmarshalJSON(data []byte) os.Error {
 ...
}

Comments

4

I'll present you another approach that I've developed. I think it's much more clean. The only downside is slightly complicated object initialization, but in usage it's very streamlined.

The main point is that you're not basing your JSON-view-object on the original object and then hiding elements in it, but the other way around, making it a part of the original object:

type CommunityBase struct {
    Name string
    Description string
}

type Community struct {
    CommunityBase
    FavoriteCount int
    Moderators []string
}

var comm = Community{CommunityBase{"Name", "Descr"}, 20, []string{"Mod1","Mod2"}}

json.Marshal(comm)
//{"Name":"Name","Description":"Descr","FavoriteCount":20,"Moderators":["Mod1","Mod2"]}

json.Marshal(comm.CommunityBase)
//{"Name":"Name","Description":"Descr"}

And that's all if you need only one view, or if your views are gradually expanded.

But if your views can't be inherited, you'll have to resort to a kind of mixins, so you can make a combined view from them:

type ThingBaseMixin struct {
    Name  string
}

type ThingVisualMixin struct {
    Color   string
    IsRound bool
}

type ThingTactileMixin struct {
    IsSoft bool
}

type Thing struct {
    ThingBaseMixin
    ThingVisualMixin
    ThingTactileMixin
    Condition string
    visualView *ThingVisualView
    tactileView *ThingTactileView
}

type ThingVisualView struct {
    *ThingBaseMixin
    *ThingVisualMixin
}

type ThingTactileView struct {
    *ThingBaseMixin
    *ThingTactileMixin
}

func main() {
    obj := Thing {
        ThingBaseMixin: ThingBaseMixin{"Bouncy Ball"},
        ThingVisualMixin: ThingVisualMixin{"blue", true},
        ThingTactileMixin: ThingTactileMixin{false},
        Condition: "Good",
    }
    obj.visualView = &ThingVisualView{&obj.ThingBaseMixin, &obj.ThingVisualMixin}
    obj.tactileView = &ThingTactileView{&obj.ThingBaseMixin, &obj.ThingTactileMixin}

    b, _ := json.Marshal(obj)
    fmt.Println(string(b))
//{"Name":"Bouncy Ball","Color":"blue","IsRound":true,"IsSoft":false,"Condition":"Good"}

    b, _ = json.Marshal(obj.ThingVisualMixin)
    fmt.Println(string(b))
//{"Color":"blue","IsRound":true}

    b, _ = json.Marshal(obj.visualView)
    fmt.Println(string(b))
//{"Name":"Bouncy Ball","Color":"blue","IsRound":true}

    b, _ = json.Marshal(obj.tactileView)
    fmt.Println(string(b))
//{"Name":"Bouncy Ball","IsSoft":false}
}

Here I've added a view into the object, but if you like, you can create it just when calling Marshal:

json.Marshal(ThingVisualView{&obj.ThingBaseMixin, &obj.ThingVisualMixin})

Or even without a preliminary type declaration:

json.Marshal(struct{*ThingBaseMixin;*ThingVisualMixin}{&obj.ThingBaseMixin,&obj.ThingVisualMixin})

2 Comments

This is a pretty interesting approach. The 2nd part of your response reminds me of PHP traits in a way.
An interesting moment is that you can edit the original object directly through the views. Maybe this approach can be of any use in some other field also.
0

Not sure why this isn't the preferred method, maybe due to the age of the post, but as far as I know, this is the 'best practice' way to handle this, with 'omitempty' tags for those which don't have to exist in the JSON object.

type Community struct {
    Name          string       `json:"name"`
    Description   string       `json:"description"` 
    Sources       *[]Source    `json:"sources,omitempty"`
    Popularity    int          `json:"popularity"`
    FavoriteCount int          `json:"favorite-count"`
    Moderators    *[]string    `json:"moderators,omitempty"`
    Children      *[]Community `json:"children,omitempty"`
    Tracks        *[]Track     `json:"tracks,omitempty"`
}

1 Comment

I think omitempty will be considered for that field every time, whereas the OP is asking about outputting same field in different ways (omitting or not) for different representation

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.