MA
MatejLach/activitystreams
ActivityStreams 2.0 encoding/decoding for Go 1.18+
activitystreams
Easy to use parser of the Activity Streams 2.0 specification in Go, especially suitable for projects implementing ActivityPub.
🚀 Quick Start
Basic Usage
package main
import (
"encoding/json"
"fmt"
"github.com/MatejLach/activitystreams"
)
func main() {
// Decode a Note object
noteJSON := `{
"type": "Note",
"content": "Hello, world!",
"published": "2023-01-01T00:00:00Z"
}`
var note activitystreams.Note
err := json.Unmarshal([]byte(noteJSON), ¬e)
if err != nil {
panic(err)
}
fmt.Printf("Note content: %s\n", note.Content)
// Encode it back to JSON
encodedNote, err := json.Marshal(¬e)
if err != nil {
panic(err)
}
fmt.Println(string(encodedNote))
}📚 Core Types
Base Types
Object- The base ActivityStreams object typeLink- The base Link typeActor- Actor types (Person, Organization, etc.)Activity- Activity types (Create, Like, Follow, etc.)IntransitiveActivity- Intransitive activities (Arrive, Travel, etc.)
Collections
Collection- Standard collectionCollectionPage- Paginated collectionOrderedCollection- Ordered collectionOrderedCollectionPage- Paginated ordered collection
Special Types
ObjectOrLink- Slice that can contain any ActivityStreams object or linkObjectOrLinkOrString- Can hold objects/links or string URLsIcons- Icon representation supporting both URL and Object formsLocation- Geographic location data
🔧 Working with Objects
Decoding
// Decode any ActivityStreams object using the generic decoder
func decodeObject(jsonData []byte) (activitystreams.ObjectLinker, error) {
var obj activitystreams.ObjectOrLink
err := json.Unmarshal(jsonData, &obj)
if err != nil {
return nil, err
}
if len(obj) > 0 {
return obj[0], nil
}
return nil, errors.New("empty object")
}
// Decode specific types
func decodeNote(jsonData []byte) (*activitystreams.Note, error) {
var note activitystreams.Note
err := json.Unmarshal(jsonData, ¬e)
return ¬e, err
}Encoding
// Encode any ActivityStreams object
func encodeObject(obj activitystreams.ObjectLinker) ([]byte, error) {
return json.Marshal(obj)
}
// Using the generic encoder
func encodeGeneric[T activitystreams.ActivityStreamer](toEncode T) ([]byte, error) {
return activitystreams.EncodeJSON(toEncode)
}📦 Working with Collections
📤 Heterogeneous slices
Slices can contain any mix of Object or Link (sub)types:
// ObjectOrLink handles heterogeneous arrays
var items activitystreams.ObjectOrLink
items = append(items, activitystreams.Note{Content: "Hello"})
items = append(items, activitystreams.Person{Name: "Alice"})
jsonBytes, err := json.Marshal(items)
// Results in JSON array with mixed typesObjectOrLink Slices
// Create and populate a heterogeneous collection
var items activitystreams.ObjectOrLink
// Add different types of objects
note := activitystreams.Note{Content: "Hello"}
person := activitystreams.Person{Name: "Alice"}
items = append(items, note)
items = append(items, person)
// Encode the collection
jsonBytes, err := json.Marshal(items)Collections with Pagination
// Create a Collection
collection := activitystreams.Collection{
TotalItems: 100,
Items: &activitystreams.ObjectOrLinkOrString{
Target: activitystreams.ObjectOrLink{
activitystreams.Note{Content: "First note"},
activitystreams.Note{Content: "Second note"},
},
},
}🔄 Generic Helper Functions
DecodeJSON
// Decode any valid ActivityStreams object
note, err := activitystreams.DecodeJSON[activitystreams.Note](strings.NewReader(noteJSON))
if err != nil {
// handle error
}
// Works with any AS type
person, err := activitystreams.DecodeJSON[activitystreams.Person](strings.NewReader(personJSON))
activity, err := activitystreams.DecodeJSON[activitystreams.Create](strings.NewReader(activityJSON))EncodeJSON
// Generic encoder for any valid ActivityStreams object
note := activitystreams.Note{Content: "Hello"}
bytes, err := activitystreams.EncodeJSON(note)
if err != nil {
// handle error
}📡 Working with Actors
Person Example
person := activitystreams.Person{
ID: "https://example.com/users/alice",
Type: "Person",
Name: "Alice Smith",
PreferredUsername: "alice",
Summary: "A person on the web",
Inbox: &activitystreams.StringWithOrderedCollection{
URL: "https://example.com/users/alice/inbox",
},
Outbox: &activitystreams.StringWithOrderedCollection{
URL: "https://example.com/users/alice/outbox",
},
}Actor Collections
// Create a followers collection
followers := activitystreams.Collection{
TotalItems: 42,
First: &activitystreams.StringWithCollectionPage{
CollectionPage: activitystreams.CollectionPage{
// pagination details
},
URL: "https://example.com/users/alice/followers?page=1",
},
}📝 Working with Activities
Create Activity Example
createActivity := activitystreams.Create{
Type: "Create",
Actor: &activitystreams.ObjectOrLinkOrString{
Target: activitystreams.ObjectOrLink{
activitystreams.Person{
ID: "https://example.com/users/alice",
Name: "Alice Smith",
},
},
},
ActivityObject: &activitystreams.ObjectOrLinkOrString{
Target: activitystreams.ObjectOrLink{
activitystreams.Note{
Content: "Hello, world!",
Published: &time.Now(),
},
},
},
}🔍 Type Checking and Conversion
Using ConcreteType
func processObject(obj activitystreams.ObjectLinker) {
reflectType, asType := activitystreams.ConcreteType(obj)
fmt.Printf("Reflection type: %s, AS type: %s\n", reflectType, asType)
switch asType {
case "Note":
if note, ok := obj.(activitystreams.Note); ok {
// Handle Note
}
case "Person":
if person, ok := obj.(activitystreams.Person); ok {
// Handle Person
}
}
}Type Assertions
// Safe type assertions
func asNote(obj activitystreams.ObjectLinker) (*activitystreams.Note, bool) {
if n, ok := obj.(activitystreams.Note); ok {
return &n, true
}
return nil, false
}
func asPerson(obj activitystreams.ObjectLinker) (*activitystreams.Person, bool) {
if p, ok := obj.(activitystreams.Person); ok {
return &p, true
}
return nil, false
}Type Analysis
func decodePayload(jsonData []byte) (activitystreams.JsonPayload, error) {
reader := bytes.NewReader(jsonData)
payloadType, err := activitystreams.DecodePayloadObjectType(reader)
if err != nil {
return payloadType, err
}
switch payloadType.Type {
case "Note":
// Handle Note
case "Person":
// Handle Person
default:
// Handle generic object or unknown type
}
return payloadType, nil
}📋 Supported Types
Object Types
ObjectArticle,Document,Audio,Video,ImageEvent,Note,PageCollection,CollectionPageOrderedCollection,OrderedCollectionPageLocation,Profile,TombstoneRelationship,Question
Link Types
LinkMention,Hashtag,PropertyValue
Actor Types
Application,Group,Organization,Person,Service
Activity Types
Accept,Add,Announce,Arrive,Block,Create,DeleteDislike,Follow,Flag,Ignore,Invite,Join,LeaveLike,Listen,Move,Offer,Read,Reject,RemoveTentativeAccept,TentativeReject,Travel,Undo,Update,View
Contributing
Contributions are welcome. If you've found a bug, or have a feature request, don't hesitate to open an issue.
On this page
Languages
Go100.0%
Contributors
GNU Affero General Public License v3.0
Created June 23, 2019
Updated October 4, 2025