Matt Bucci
Matt Bucci

Reputation: 2140

One struct with multiple json representations

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?

Upvotes: 21

Views: 18065

Answers (5)

David Napier
David Napier

Reputation: 1

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"`
}

Upvotes: 0

fabmilo
fabmilo

Reputation: 48330

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 {
 ...
}

Upvotes: 5

[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,
})

Upvotes: 23

Michael Weibel
Michael Weibel

Reputation: 2962

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)

Upvotes: 6

user
user

Reputation: 25818

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})

Upvotes: 4

Related Questions