Liza Shulyayeva

`json.Marshal()` with additional fields in Go



I have a Go struct that I want to encode to JSON with some additional fields.

The struct looks like this:

type Actor struct {
	Context           []Context  `bson:"@context,omitempty" json:"@context,omitempty"`
	Kind              Kind       `bson:"type,omitempty" json:"type,omitempty"`
	ID                string     `bson:"id,omitempty" json:"id,omitempty"`
	PreferredUsername string     `bson:"preferredUsername,omitempty" json:"preferredUsername,omitempty"`
	Name              string     `bson:"name,omitempty" json:"name,omitempty"`
	Summary           string     `bson:"summary,omitempty" json:"summary,omitempty"`
	Icon              []string   `bson:"icon,omitempty" json:"icon,omitempty"`
	PublicKey         *PublicKey `bson:"-" json:"publicKey,omitempty"`
	url               *url.URL
}

I want to add additional "following", "followers", "liked", "inbox", and "outbox" fields to the encoded JSON. These are strings obtained as follows:

func (a *Actor) Following() string {
	url := a.getURL()
	url.Path = path.Join(url.Path, EndpointFollowing)
	return url.String()
}

func (a *Actor) Followers() string {
	url := a.getURL()
	url.Path = path.Join(url.Path, EndpointFollowers)
	return url.String()
}

func (a *Actor) Liked() string {
	url := a.getURL()
	url.Path = path.Join(url.Path, EndpointLiked)
	return url.String()
}

func (a *Actor) Inbox() string {
	url := a.getURL()
	url.Path = path.Join(url.Path, EndpointInbox)
	return url.String()
}

func (a *Actor) Outbox() string {
	url := a.getURL()
	url.Path = path.Join(url.Path, EndpointOutbox)
	return url.String()
}

I ended up doing this by overriding MarshalJSON() and creating a new struct with an anonymous Actor field plus the additional fields:

func (a *Actor) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		Actor
		Following string `json:"following"`
		Followers string `json:"followers"`
		Liked     string `json:"liked"`
		Inbox     string `json:"inbox"`
		Outbox    string `json:"outbox"`
	}{
		Actor:     *a,
		Following: a.Following(),
		Followers: a.Followers(),
		Liked:     a.Liked(),
		Inbox:     a.Inbox(),
		Outbox:    a.Outbox(),
	})
}

As a result, this instance of Actor:

actor = &Actor{
				Context:           []Context{CtxActivityStreams},
				Kind:              KindPerson,
				ID:                "https://social.example/alyssa",
				PreferredUsername: "alyssa",
			}

Gets encoded into this when calling json.Marshal(actor):

{"@context": ["https://www.w3.org/ns/activitystreams"],
					 "type": "Person",
					 "id": "https://social.example/alyssa",
					 "preferredUsername": "alyssa",
					 "inbox": "https://social.example/alyssa/inbox.json",
					 "outbox": "https://social.example/alyssa/outbox.json",
					 "followers": "https://social.example/alyssa/followers.json",
					 "following": "https://social.example/alyssa/following.json",
					 "liked": "https://social.example/alyssa/liked.json"}

Which can be tested like this (only showing one test case for the purpose of this post):

func TestActorMarshal(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		name              string
		preferredUsername string
		wantJSON          string
	}{
		{
			name:              "success",
			preferredUsername: "alyssa",
			wantJSON: fmt.Sprintf(`{"@context": ["https://www.w3.org/ns/activitystreams"],
					 "type": "%s",
					 "id": "https://social.example/alyssa",
					 "preferredUsername": "alyssa",
					 "inbox": "https://social.example/alyssa/inbox.json",
					 "outbox": "https://social.example/alyssa/outbox.json",
					 "followers": "https://social.example/alyssa/followers.json",
					 "following": "https://social.example/alyssa/following.json",
					 "liked": "https://social.example/alyssa/liked.json"}`, KindPerson),
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			actor, err := NewActor(KindPerson, "https://social.example/", "alyssa")
			require.NoError(t, err)

			gotJSON, err := json.Marshal(actor)
			require.NoError(t, err)
			require.JSONEq(t, tc.wantJSON, string(gotJSON))
		})
	}
}
© 2022 · Liza Shulyayeva · Top · RSS · privacy policy