Use fastjson properly

This commit is contained in:
Marius Orcsik 2021-08-15 13:41:01 +02:00
parent c8d01274d3
commit e26b856fcc
No known key found for this signature in database
GPG key ID: DBF5E47F5DBC4D21
18 changed files with 409 additions and 281 deletions

View file

@ -6,6 +6,8 @@ import (
"strings"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// Activity Types
@ -716,7 +718,12 @@ func ActivityNew(id ID, typ ActivityVocabularyType, ob Item) *Activity {
// UnmarshalJSON
func (a *Activity) UnmarshalJSON(data []byte) error {
return loadActivity(data, a)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadActivity(val, a)
}
// ToActivity

View file

@ -334,7 +334,12 @@ func (a *Actor) Clean() {
}
func (a *Actor) UnmarshalJSON(data []byte) error {
return loadActor(data, a)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadActor(val, a)
}
func (a Actor) MarshalJSON() ([]byte, error) {
@ -419,12 +424,17 @@ type Endpoints struct {
// UnmarshalJSON
func (e *Endpoints) UnmarshalJSON(data []byte) error {
e.OauthAuthorizationEndpoint = JSONGetItem(data, "oauthAuthorizationEndpoint")
e.OauthTokenEndpoint = JSONGetItem(data, "oauthTokenEndpoint")
e.UploadMedia = JSONGetItem(data, "uploadMedia")
e.ProvideClientKey = JSONGetItem(data, "provideClientKey")
e.SignClientKey = JSONGetItem(data, "signClientKey")
e.SharedInbox = JSONGetItem(data, "sharedInbox")
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
e.OauthAuthorizationEndpoint = JSONGetItem(val, "oauthAuthorizationEndpoint")
e.OauthTokenEndpoint = JSONGetItem(val, "oauthTokenEndpoint")
e.UploadMedia = JSONGetItem(val, "uploadMedia")
e.ProvideClientKey = JSONGetItem(val, "provideClientKey")
e.SignClientKey = JSONGetItem(val, "signClientKey")
e.SharedInbox = JSONGetItem(val, "sharedInbox")
return nil
}

View file

@ -5,6 +5,8 @@ import (
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
const CollectionOfItems ActivityVocabularyType = "ItemCollection"
@ -233,7 +235,12 @@ func (c Collection) Contains(r Item) bool {
// UnmarshalJSON
func (c *Collection) UnmarshalJSON(data []byte) error {
return loadCollection(data, c)
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return loadCollection(val, c)
}
// MarshalJSON

View file

@ -4,6 +4,8 @@ import (
"errors"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// CollectionPage is a Collection that contains a large number of items and when it becomes impractical
@ -188,7 +190,12 @@ func (c CollectionPage) Contains(r Item) bool {
// UnmarshalJSON
func (c *CollectionPage) UnmarshalJSON(data []byte) error {
return loadCollectionPage(data, c)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadCollectionPage(val, c)
}
// MarshalJSON

View file

@ -1,12 +1,12 @@
package activitypub
import (
"bytes"
"encoding"
"encoding/json"
"fmt"
"net/url"
"reflect"
"strings"
"time"
"github.com/valyala/fastjson"
@ -26,58 +26,50 @@ var ItemTyperFunc TyperFn = GetItemByType
// for a specific ActivityVocabularyType
type TyperFn func(ActivityVocabularyType) (Item, error)
func JSONGetID(data []byte) ID {
i := fastjson.GetString(data, "id")
func JSONGetID(val *fastjson.Value) ID {
i := val.Get("id").GetStringBytes()
return ID(i)
}
func JSONGetType(data []byte) ActivityVocabularyType {
t := fastjson.GetBytes(data, "type")
func JSONGetType(val *fastjson.Value) ActivityVocabularyType {
t := val.Get("type").GetStringBytes()
return ActivityVocabularyType(t)
}
func JSONGetMimeType(data []byte, prop string) MimeType {
t := fastjson.GetString(data, prop)
func JSONGetMimeType(val *fastjson.Value, prop string) MimeType {
t := val.Get(prop).GetStringBytes()
return MimeType(t)
}
func JSONGetInt(data []byte, prop string) int64 {
if len(data) == 0 {
return 0
}
val := fastjson.GetInt(data, prop)
return int64(val)
func JSONGetInt(val *fastjson.Value, prop string) int64 {
i := val.Get(prop).GetInt64()
return i
}
func JSONGetFloat(data []byte, prop string) float64 {
if len(data) == 0 {
return 0
}
val := fastjson.GetFloat64(data, prop)
return val
func JSONGetFloat(val *fastjson.Value, prop string) float64 {
f := val.Get(prop).GetFloat64()
return f
}
func JSONGetString(data []byte, prop string) string {
val := fastjson.GetString(data, prop)
return val
func JSONGetString(val *fastjson.Value, prop string) string {
s := val.Get(prop).GetStringBytes()
return string(s)
}
func JSONGetBytes(data []byte, prop string) []byte {
val := fastjson.GetBytes(data, prop)
return val
func JSONGetBytes(val *fastjson.Value, prop string) []byte {
s := val.Get(prop).GetStringBytes()
return s
}
func JSONGetBoolean(data []byte, prop string) bool {
val := fastjson.GetBool(data, prop)
return val
func JSONGetBoolean(val *fastjson.Value, prop string) bool {
t, _ := val.Get(prop).Bool()
return t
}
func JSONGetNaturalLanguageField(data []byte, prop string) NaturalLanguageValues {
func JSONGetNaturalLanguageField(val *fastjson.Value, prop string) NaturalLanguageValues {
n := NaturalLanguageValues{}
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return nil
if val == nil {
return n
}
v := val.Get(prop)
if v == nil {
@ -105,44 +97,44 @@ func JSONGetNaturalLanguageField(data []byte, prop string) NaturalLanguageValues
return n
}
func JSONGetTime(data []byte, prop string) time.Time {
func JSONGetTime(val *fastjson.Value, prop string) time.Time {
t := time.Time{}
if str := fastjson.GetBytes(data, prop); len(str) > 0 {
if val == nil {
return t
}
if str := val.Get(prop).GetStringBytes(); len(str) > 0 {
t.UnmarshalText(str)
return t.UTC()
}
return t
}
func JSONGetDuration(data []byte, prop string) time.Duration {
if str := fastjson.GetString(data, prop); len(str) > 0 {
func JSONGetDuration(val *fastjson.Value, prop string) time.Duration {
if str := val.Get(prop).GetStringBytes(); len(str) > 0 {
// TODO(marius): this needs to be replaced to be compatible with xsd:duration
d, _ := time.ParseDuration(str)
d, _ := time.ParseDuration(string(str))
return d
}
return 0
}
func JSONGetPublicKey(data []byte, prop string) PublicKey {
func JSONGetPublicKey(val *fastjson.Value, prop string) PublicKey {
key := PublicKey{}
key.UnmarshalJSON(JSONGetBytes(data, prop))
key.UnmarshalJSON(JSONGetBytes(val, prop))
return key
}
func JSONGetStreams(data []byte, prop string) []ItemCollection {
func JSONGetStreams(val *fastjson.Value, prop string) []ItemCollection {
// TODO(marius)
return nil
}
func itemFn(data []byte) (Item, error) {
if len(data) == 0 {
return nil, nil
}
typ := JSONGetType(data)
func itemFn(val *fastjson.Value) (Item, error) {
typ := JSONGetType(val)
if typ == "" {
// try to see if it's an IRI
if i, ok := asIRI(data); ok {
if i, ok := asIRI(val); ok {
return i, nil
}
}
@ -151,32 +143,81 @@ func itemFn(data []byte) (Item, error) {
return nil, nil
}
p := reflect.PtrTo(reflect.TypeOf(i))
if reflect.TypeOf(i).Implements(unmarshalerType) || p.Implements(unmarshalerType) {
err = i.(json.Unmarshaler).UnmarshalJSON(data)
}
if reflect.TypeOf(i).Implements(textUnmarshalerType) || p.Implements(textUnmarshalerType) {
err = i.(encoding.TextUnmarshaler).UnmarshalText(data)
switch typ {
case ObjectType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, VideoType:
err = OnObject(i, func(ob *Object) error {
return loadObject(val, ob)
})
case LinkType, MentionType:
err = OnLink(i, func(l *Link) error {
return loadLink(val, l)
})
case ActivityType, AcceptType, AddType, AnnounceType, BlockType, CreateType, DeleteType, DislikeType,
FlagType, FollowType, IgnoreType, InviteType, JoinType, LeaveType, LikeType, ListenType, MoveType, OfferType,
RejectType, ReadType, RemoveType, TentativeRejectType, TentativeAcceptType, UndoType, UpdateType, ViewType:
err = OnActivity(i, func(act *Activity) error {
return loadActivity(val, act)
})
case IntransitiveActivityType, ArriveType, TravelType:
err = OnIntransitiveActivity(i, func(act *IntransitiveActivity) error {
return loadIntransitiveActivity(val, act)
})
case ActorType, ArticleType, ApplicationType, GroupType, OrganizationType, PersonType, ServiceType:
err = OnActor(i, func(a *Actor) error {
return loadActor(val, a)
})
case CollectionType:
err = OnCollection(i, func(c *Collection) error {
return loadCollection(val, c)
})
case OrderedCollectionType:
err = OnOrderedCollection(i, func(c *OrderedCollection) error {
return loadOrderedCollection(val, c)
})
case CollectionPageType:
err = OnCollectionPage(i, func(p *CollectionPage) error {
return loadCollectionPage(val, p)
})
case OrderedCollectionPageType:
err = OnOrderedCollectionPage(i, func(p *OrderedCollectionPage) error {
return loadOrderedCollectionPage(val, p)
})
case PlaceType:
err = OnPlace(i, func(p *Place) error {
return loadPlace(val, p)
})
case ProfileType:
err = OnProfile(i, func(p *Profile) error {
return loadProfile(val, p)
})
case RelationshipType:
err = OnRelationship(i, func(r *Relationship) error {
return loadRelationship(val, r)
})
case TombstoneType:
err = OnTombstone(i, func(t *Tombstone) error {
return loadTombstone(val, t)
})
case QuestionType:
err = OnQuestion(i, func(q *Question) error {
return loadQuestion(val, q)
})
}
if !NotEmpty(i) {
return nil, nil
}
return i, err
}
func JSONUnmarshalToItem(data []byte) Item {
if len(data) == 0 {
return nil
}
func JSONUnmarshalToItem(val *fastjson.Value) Item {
if ItemTyperFunc == nil {
return nil
}
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return nil
}
var i Item
var (
i Item
err error
)
switch val.Type() {
case fastjson.TypeArray:
items := make(ItemCollection, 0)
@ -185,7 +226,7 @@ func JSONUnmarshalToItem(data []byte) Item {
// NOTE(marius): I'm sure that using v.String here slows us down and undoes any benefits that fastjson
// might bring
v.Object()
it, err = itemFn([]byte(v.String()))
it, err = itemFn(v)
if it != nil && err == nil {
items.Append(it)
}
@ -198,9 +239,9 @@ func JSONUnmarshalToItem(data []byte) Item {
i = items
}
case fastjson.TypeObject:
i, err = itemFn(data)
i, err = itemFn(val)
case fastjson.TypeString:
if iri, ok := asIRI(data); ok {
if iri, ok := asIRI(val); ok {
// try to see if it's an IRI
i = iri
}
@ -211,39 +252,33 @@ func JSONUnmarshalToItem(data []byte) Item {
return i
}
func asIRI(val []byte) (IRI, bool) {
if len(val) == 0 {
func asIRI(val *fastjson.Value) (IRI, bool) {
if val == nil {
return NilIRI, true
}
val = bytes.Trim(val, string('"'))
u, err := url.ParseRequestURI(string(val))
s := strings.Trim(val.String(), `"`)
u, err := url.ParseRequestURI(s)
if err == nil && len(u.Scheme) > 0 && len(u.Host) > 0 {
// try to see if it's an IRI
return IRI(val), true
return IRI(s), true
}
return EmptyIRI, false
}
func JSONGetItem(data []byte, prop string) Item {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return nil
}
val = val.Get(prop)
if val == nil {
func JSONGetItem(val *fastjson.Value, prop string) Item {
if val = val.Get(prop); val == nil {
return nil
}
switch val.Type() {
case fastjson.TypeString:
if i, ok := asIRI(val.GetStringBytes()); ok {
if i, ok := asIRI(val); ok {
// try to see if it's an IRI
return i
}
case fastjson.TypeArray:
return JSONGetItems(data, prop)
return JSONGetItems(val, prop)
case fastjson.TypeObject:
return JSONUnmarshalToItem(val.GetStringBytes())
return JSONUnmarshalToItem(val)
case fastjson.TypeNumber:
fallthrough
case fastjson.TypeNull:
@ -254,12 +289,7 @@ func JSONGetItem(data []byte, prop string) Item {
return nil
}
func JSONGetURIItem(data []byte, prop string) Item {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return nil
}
func JSONGetURIItem(val *fastjson.Value, prop string) Item {
v := val.Get(prop)
if v == nil {
return nil
@ -267,24 +297,15 @@ func JSONGetURIItem(data []byte, prop string) Item {
switch v.Type() {
case fastjson.TypeObject:
return JSONGetItem(data, prop)
return JSONGetItem(val, prop)
case fastjson.TypeArray:
it := make(ItemCollection, 0)
for _, ob := range v.GetArray() {
value := ob.GetStringBytes()
if i, ok := asIRI(value); ok {
it.Append(i)
continue
}
i, err := ItemTyperFunc(JSONGetType(value))
for _, val := range v.GetArray() {
i, err := itemFn(val)
if err != nil {
continue
}
if err = i.(json.Unmarshaler).UnmarshalJSON(value); err != nil {
continue
}
it.Append(i)
}
return it
case fastjson.TypeString:
@ -294,32 +315,24 @@ func JSONGetURIItem(data []byte, prop string) Item {
return nil
}
func JSONGetItems(data []byte, prop string) ItemCollection {
if len(data) == 0 {
return nil
}
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return nil
}
val = val.Get(prop)
func JSONGetItems(val *fastjson.Value, prop string) ItemCollection {
if val == nil {
return nil
}
if val = val.Get(prop); val == nil {
return nil
}
it := make(ItemCollection, 0)
switch val.Type() {
case fastjson.TypeArray:
for _, v := range val.GetArray() {
if i, err := itemFn([]byte(v.String())); i != nil && err == nil {
if i, err := itemFn(v); i != nil && err == nil {
it.Append(i)
}
}
case fastjson.TypeObject:
if i := JSONGetItem(data, prop); i != nil {
if i := JSONGetItem(val, prop); i != nil {
it.Append(i)
}
case fastjson.TypeString:
@ -333,14 +346,14 @@ func JSONGetItems(data []byte, prop string) ItemCollection {
return it
}
func JSONGetLangRefField(data []byte, prop string) LangRef {
val := fastjson.GetString(data, prop)
return LangRef(val)
func JSONGetLangRefField(val *fastjson.Value, prop string) LangRef {
s := val.Get(prop).GetStringBytes()
return LangRef(s)
}
func JSONGetIRI(data []byte, prop string) IRI {
val := fastjson.GetString(data, prop)
return IRI(val)
func JSONGetIRI(val *fastjson.Value, prop string) IRI {
s := val.Get(prop).GetStringBytes()
return IRI(s)
}
// UnmarshalJSON tries to detect the type of the object in the json data and then outputs a matching
@ -349,7 +362,10 @@ func UnmarshalJSON(data []byte) (Item, error) {
if ItemTyperFunc == nil {
ItemTyperFunc = GetItemByType
}
return JSONUnmarshalToItem(data), nil
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
return JSONUnmarshalToItem(val), err
}
func GetItemByType(typ ActivityVocabularyType) (Item, error) {
@ -471,8 +487,8 @@ func GetItemByType(typ ActivityVocabularyType) (Item, error) {
return nil, fmt.Errorf("empty ActivityStreams type")
}
func JSONGetActorEndpoints(data []byte, prop string) *Endpoints {
str := fastjson.GetBytes(data, prop)
func JSONGetActorEndpoints(val *fastjson.Value, prop string) *Endpoints {
str := val.Get(prop).GetStringBytes()
var e *Endpoints
if len(str) > 0 {
@ -483,204 +499,204 @@ func JSONGetActorEndpoints(data []byte, prop string) *Endpoints {
return e
}
func loadObject(data []byte, o *Object) error {
func loadObject(val *fastjson.Value, o *Object) error {
if ItemTyperFunc == nil {
ItemTyperFunc = GetItemByType
}
o.ID = JSONGetID(data)
o.Type = JSONGetType(data)
o.Name = JSONGetNaturalLanguageField(data, "name")
o.Content = JSONGetNaturalLanguageField(data, "content")
o.Summary = JSONGetNaturalLanguageField(data, "summary")
o.Context = JSONGetItem(data, "context")
o.URL = JSONGetURIItem(data, "url")
o.MediaType = JSONGetMimeType(data, "mediaType")
o.Generator = JSONGetItem(data, "generator")
o.AttributedTo = JSONGetItem(data, "attributedTo")
o.Attachment = JSONGetItem(data, "attachment")
o.Location = JSONGetItem(data, "location")
o.Published = JSONGetTime(data, "published")
o.StartTime = JSONGetTime(data, "startTime")
o.EndTime = JSONGetTime(data, "endTime")
o.Duration = JSONGetDuration(data, "duration")
o.Icon = JSONGetItem(data, "icon")
o.Preview = JSONGetItem(data, "preview")
o.Image = JSONGetItem(data, "image")
o.Updated = JSONGetTime(data, "updated")
o.InReplyTo = JSONGetItem(data, "inReplyTo")
o.To = JSONGetItems(data, "to")
o.Audience = JSONGetItems(data, "audience")
o.Bto = JSONGetItems(data, "bto")
o.CC = JSONGetItems(data, "cc")
o.BCC = JSONGetItems(data, "bcc")
o.Replies = JSONGetItem(data, "replies")
o.Tag = JSONGetItems(data, "tag")
o.Likes = JSONGetItem(data, "likes")
o.Shares = JSONGetItem(data, "shares")
o.Source = GetAPSource(data)
o.ID = JSONGetID(val)
o.Type = JSONGetType(val)
o.Name = JSONGetNaturalLanguageField(val, "name")
o.Content = JSONGetNaturalLanguageField(val, "content")
o.Summary = JSONGetNaturalLanguageField(val, "summary")
o.Context = JSONGetItem(val, "context")
o.URL = JSONGetURIItem(val, "url")
o.MediaType = JSONGetMimeType(val, "mediaType")
o.Generator = JSONGetItem(val, "generator")
o.AttributedTo = JSONGetItem(val, "attributedTo")
o.Attachment = JSONGetItem(val, "attachment")
o.Location = JSONGetItem(val, "location")
o.Published = JSONGetTime(val, "published")
o.StartTime = JSONGetTime(val, "startTime")
o.EndTime = JSONGetTime(val, "endTime")
o.Duration = JSONGetDuration(val, "duration")
o.Icon = JSONGetItem(val, "icon")
o.Preview = JSONGetItem(val, "preview")
o.Image = JSONGetItem(val, "image")
o.Updated = JSONGetTime(val, "updated")
o.InReplyTo = JSONGetItem(val, "inReplyTo")
o.To = JSONGetItems(val, "to")
o.Audience = JSONGetItems(val, "audience")
o.Bto = JSONGetItems(val, "bto")
o.CC = JSONGetItems(val, "cc")
o.BCC = JSONGetItems(val, "bcc")
o.Replies = JSONGetItem(val, "replies")
o.Tag = JSONGetItems(val, "tag")
o.Likes = JSONGetItem(val, "likes")
o.Shares = JSONGetItem(val, "shares")
o.Source = GetAPSource(val)
return nil
}
func loadIntransitiveActivity(data []byte, i *IntransitiveActivity) error {
func loadIntransitiveActivity(val *fastjson.Value, i *IntransitiveActivity) error {
OnObject(i, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
i.Actor = JSONGetItem(data, "actor")
i.Target = JSONGetItem(data, "target")
i.Result = JSONGetItem(data, "result")
i.Origin = JSONGetItem(data, "origin")
i.Instrument = JSONGetItem(data, "instrument")
i.Actor = JSONGetItem(val, "actor")
i.Target = JSONGetItem(val, "target")
i.Result = JSONGetItem(val, "result")
i.Origin = JSONGetItem(val, "origin")
i.Instrument = JSONGetItem(val, "instrument")
return nil
}
func loadActivity(data []byte, a *Activity) error {
func loadActivity(val *fastjson.Value, a *Activity) error {
OnIntransitiveActivity(a, func(i *IntransitiveActivity) error {
return loadIntransitiveActivity(data, i)
return loadIntransitiveActivity(val, i)
})
a.Object = JSONGetItem(data, "object")
a.Object = JSONGetItem(val, "object")
return nil
}
func loadQuestion(data []byte, q *Question) error {
func loadQuestion(val *fastjson.Value, q *Question) error {
OnIntransitiveActivity(q, func(i *IntransitiveActivity) error {
return loadIntransitiveActivity(data, i)
return loadIntransitiveActivity(val, i)
})
q.OneOf = JSONGetItem(data, "oneOf")
q.AnyOf = JSONGetItem(data, "anyOf")
q.Closed = JSONGetBoolean(data, "closed")
q.OneOf = JSONGetItem(val, "oneOf")
q.AnyOf = JSONGetItem(val, "anyOf")
q.Closed = JSONGetBoolean(val, "closed")
return nil
}
func loadActor(data []byte, a *Actor) error {
func loadActor(val *fastjson.Value, a *Actor) error {
OnObject(a, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
a.PreferredUsername = JSONGetNaturalLanguageField(data, "preferredUsername")
a.Followers = JSONGetItem(data, "followers")
a.Following = JSONGetItem(data, "following")
a.Inbox = JSONGetItem(data, "inbox")
a.Outbox = JSONGetItem(data, "outbox")
a.Liked = JSONGetItem(data, "liked")
a.Endpoints = JSONGetActorEndpoints(data, "endpoints")
a.Streams = JSONGetStreams(data, "streams")
a.PublicKey = JSONGetPublicKey(data, "publicKey")
a.PreferredUsername = JSONGetNaturalLanguageField(val, "preferredUsername")
a.Followers = JSONGetItem(val, "followers")
a.Following = JSONGetItem(val, "following")
a.Inbox = JSONGetItem(val, "inbox")
a.Outbox = JSONGetItem(val, "outbox")
a.Liked = JSONGetItem(val, "liked")
a.Endpoints = JSONGetActorEndpoints(val, "endpoints")
a.Streams = JSONGetStreams(val, "streams")
a.PublicKey = JSONGetPublicKey(val, "publicKey")
return nil
}
func loadCollection(data []byte, c *Collection) error {
func loadCollection(val *fastjson.Value, c *Collection) error {
OnObject(c, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
c.Current = JSONGetItem(data, "current")
c.First = JSONGetItem(data, "first")
c.Last = JSONGetItem(data, "last")
c.TotalItems = uint(JSONGetInt(data, "totalItems"))
c.Items = JSONGetItems(data, "items")
c.Current = JSONGetItem(val, "current")
c.First = JSONGetItem(val, "first")
c.Last = JSONGetItem(val, "last")
c.TotalItems = uint(JSONGetInt(val, "totalItems"))
c.Items = JSONGetItems(val, "items")
return nil
}
func loadCollectionPage(data []byte, c *CollectionPage) error {
func loadCollectionPage(val *fastjson.Value, c *CollectionPage) error {
OnCollection(c, func(c *Collection) error {
return loadCollection(data, c)
return loadCollection(val, c)
})
c.Next = JSONGetItem(data, "next")
c.Prev = JSONGetItem(data, "prev")
c.PartOf = JSONGetItem(data, "partOf")
c.Next = JSONGetItem(val, "next")
c.Prev = JSONGetItem(val, "prev")
c.PartOf = JSONGetItem(val, "partOf")
return nil
}
func loadOrderedCollection(data []byte, c *OrderedCollection) error {
func loadOrderedCollection(val *fastjson.Value, c *OrderedCollection) error {
OnObject(c, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
c.Current = JSONGetItem(data, "current")
c.First = JSONGetItem(data, "first")
c.Last = JSONGetItem(data, "last")
c.TotalItems = uint(JSONGetInt(data, "totalItems"))
c.OrderedItems = JSONGetItems(data, "orderedItems")
c.Current = JSONGetItem(val, "current")
c.First = JSONGetItem(val, "first")
c.Last = JSONGetItem(val, "last")
c.TotalItems = uint(JSONGetInt(val, "totalItems"))
c.OrderedItems = JSONGetItems(val, "orderedItems")
return nil
}
func loadOrderedCollectionPage(data []byte, c *OrderedCollectionPage) error {
func loadOrderedCollectionPage(val *fastjson.Value, c *OrderedCollectionPage) error {
OnOrderedCollection(c, func(c *OrderedCollection) error {
return loadOrderedCollection(data, c)
return loadOrderedCollection(val, c)
})
c.Next = JSONGetItem(data, "next")
c.Prev = JSONGetItem(data, "prev")
c.PartOf = JSONGetItem(data, "partOf")
c.StartIndex = uint(JSONGetInt(data, "startIndex"))
c.Next = JSONGetItem(val, "next")
c.Prev = JSONGetItem(val, "prev")
c.PartOf = JSONGetItem(val, "partOf")
c.StartIndex = uint(JSONGetInt(val, "startIndex"))
return nil
}
func loadPlace(data []byte, p *Place) error {
func loadPlace(val *fastjson.Value, p *Place) error {
OnObject(p, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
p.Accuracy = JSONGetFloat(data, "accuracy")
p.Altitude = JSONGetFloat(data, "altitude")
p.Latitude = JSONGetFloat(data, "latitude")
p.Longitude = JSONGetFloat(data, "longitude")
p.Radius = JSONGetInt(data, "radius")
p.Units = JSONGetString(data, "units")
p.Accuracy = JSONGetFloat(val, "accuracy")
p.Altitude = JSONGetFloat(val, "altitude")
p.Latitude = JSONGetFloat(val, "latitude")
p.Longitude = JSONGetFloat(val, "longitude")
p.Radius = JSONGetInt(val, "radius")
p.Units = JSONGetString(val, "units")
return nil
}
func loadProfile(data []byte, p *Profile) error {
func loadProfile(val *fastjson.Value, p *Profile) error {
OnObject(p, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
p.Describes = JSONGetItem(data, "describes")
p.Describes = JSONGetItem(val, "describes")
return nil
}
func loadRelationship(data []byte, r *Relationship) error {
func loadRelationship(val *fastjson.Value, r *Relationship) error {
OnObject(r, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
r.Subject = JSONGetItem(data, "subject")
r.Object = JSONGetItem(data, "object")
r.Relationship = JSONGetItem(data, "relationship")
r.Subject = JSONGetItem(val, "subject")
r.Object = JSONGetItem(val, "object")
r.Relationship = JSONGetItem(val, "relationship")
return nil
}
func loadTombstone(data []byte, t *Tombstone) error {
func loadTombstone(val *fastjson.Value, t *Tombstone) error {
OnObject(t, func(o *Object) error {
return loadObject(data, o)
return loadObject(val, o)
})
t.FormerType = ActivityVocabularyType(JSONGetString(data, "formerType"))
t.Deleted = JSONGetTime(data, "deleted")
t.FormerType = ActivityVocabularyType(JSONGetString(val, "formerType"))
t.Deleted = JSONGetTime(val, "deleted")
return nil
}
func loadLink(data []byte, l *Link) error {
func loadLink(val *fastjson.Value, l *Link) error {
if ItemTyperFunc == nil {
ItemTyperFunc = GetItemByType
}
l.ID = JSONGetID(data)
l.Type = JSONGetType(data)
l.MediaType = JSONGetMimeType(data, "mediaType")
l.Preview = JSONGetItem(data, "preview")
h := JSONGetInt(data, "height")
l.ID = JSONGetID(val)
l.Type = JSONGetType(val)
l.MediaType = JSONGetMimeType(val, "mediaType")
l.Preview = JSONGetItem(val, "preview")
h := JSONGetInt(val, "height")
if h != 0 {
l.Height = uint(h)
}
w := JSONGetInt(data, "width")
w := JSONGetInt(val, "width")
if w != 0 {
l.Width = uint(w)
}
l.Name = JSONGetNaturalLanguageField(data, "name")
hrefLang := JSONGetLangRefField(data, "hrefLang")
l.Name = JSONGetNaturalLanguageField(val, "name")
hrefLang := JSONGetLangRefField(val, "hrefLang")
if len(hrefLang) > 0 {
l.HrefLang = hrefLang
}
href := JSONGetURIItem(data, "href")
href := JSONGetURIItem(val, "href")
if href != nil {
ll := href.GetLink()
if len(ll) > 0 {
l.Href = ll
}
}
rel := JSONGetURIItem(data, "rel")
rel := JSONGetURIItem(val, "rel")
if rel != nil {
rr := rel.GetLink()
if len(rr) > 0 {

View file

@ -363,23 +363,23 @@ func TestUnmarshalJSON(t *testing.T) {
First: IRI("https://federated.git/inbox?maxItems=100"),
OrderedItems: ItemCollection{
&Activity{
ID: "https://federated.git/activities/07440c39-64b2-4492-89cf-f5c2872cf4ff",
Type: CreateType,
ID: "https://federated.git/activities/07440c39-64b2-4492-89cf-f5c2872cf4ff",
Type: CreateType,
AttributedTo: IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91"),
To: ItemCollection{PublicNS},
CC: ItemCollection{ IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91/followers") },
Published: time.Date(2021,8,8, 16, 9, 5, 0, time.UTC),
Actor: IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91"),
Object: IRI("https://federated.git/objects/3eb69f77-3b08-4bf1-8760-c7333e2900c4"),
To: ItemCollection{PublicNS},
CC: ItemCollection{IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91/followers")},
Published: time.Date(2021, 8, 8, 16, 9, 5, 0, time.UTC),
Actor: IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91"),
Object: IRI("https://federated.git/objects/3eb69f77-3b08-4bf1-8760-c7333e2900c4"),
},
&Activity{
ID: "https://federated.git/activities/ab9a5511-cdb5-4585-8a48-775d1bf20121",
Type: LikeType,
ID: "https://federated.git/activities/ab9a5511-cdb5-4585-8a48-775d1bf20121",
Type: LikeType,
AttributedTo: IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91"),
To: ItemCollection{PublicNS, IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91")},
Published: time.Date(2021,8,8, 16, 9, 5, 0, time.UTC),
Actor: IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91"),
Object: IRI("https://federated.git/objects/3eb69f77-3b08-4bf1-8760-c7333e2900c4"),
To: ItemCollection{PublicNS, IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91")},
Published: time.Date(2021, 8, 8, 16, 9, 5, 0, time.UTC),
Actor: IRI("https://federated.git/actors/b1757243-080a-49dc-b832-42905d554b91"),
Object: IRI("https://federated.git/objects/3eb69f77-3b08-4bf1-8760-c7333e2900c4"),
},
},
TotalItems: 2,

View file

@ -5,6 +5,8 @@ import (
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// IntransitiveActivity Instances of IntransitiveActivity are a subtype of Activity representing intransitive actions.
@ -177,7 +179,12 @@ func (i IntransitiveActivity) IsCollection() bool {
// UnmarshalJSON
func (i *IntransitiveActivity) UnmarshalJSON(data []byte) error {
return loadIntransitiveActivity(data, i)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadIntransitiveActivity(val, i)
}
// MarshalJSON

10
iri.go
View file

@ -156,18 +156,18 @@ func (i *IRIs) UnmarshalJSON(data []byte) error {
return nil
}
p := fastjson.Parser{}
value, err := p.ParseBytes(data)
val, err := p.ParseBytes(data)
if err != nil {
return err
}
switch value.Type() {
switch val.Type() {
case fastjson.TypeString:
if iri, ok := asIRI(value.GetStringBytes()); ok && len(iri) > 0 {
if iri, ok := asIRI(val); ok && len(iri) > 0 {
*i = append(*i, iri)
}
case fastjson.TypeArray:
for _, v := range value.GetArray() {
if iri, ok := asIRI(v.GetStringBytes()); ok && len(iri) > 0 {
for _, v := range val.GetArray() {
if iri, ok := asIRI(v); ok && len(iri) > 0 {
*i = append(*i, iri)
}
}

View file

@ -3,6 +3,8 @@ package activitypub
import (
"errors"
"fmt"
"github.com/valyala/fastjson"
)
var LinkTypes = ActivityVocabularyTypes{
@ -104,7 +106,12 @@ func (l Link) MarshalJSON() ([]byte, error) {
// UnmarshalJSON
func (l *Link) UnmarshalJSON(data []byte) error {
return loadLink(data, l)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadLink(val, l)
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.

View file

@ -280,7 +280,12 @@ func (o Object) IsCollection() bool {
// UnmarshalJSON
func (o *Object) UnmarshalJSON(data []byte) error {
return loadObject(data, o)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadObject(val, o)
}
// MarshalJSON
@ -478,13 +483,16 @@ type Source struct {
}
// GetAPSource
func GetAPSource(data []byte) Source {
func GetAPSource(val *fastjson.Value) Source {
s := Source{}
if val == nil {
return s
}
if contBytes := fastjson.GetBytes(data, "source", "content"); len(contBytes) > 0 {
if contBytes := val.Get("source", "content").GetStringBytes(); len(contBytes) > 0 {
s.Content.UnmarshalJSON(contBytes)
}
if mimeBytes := fastjson.GetBytes(data, "source", "mediaType"); len(mimeBytes) > 0 {
if mimeBytes := val.Get("source", "mediaType").GetStringBytes(); len(mimeBytes) > 0 {
s.MediaType.UnmarshalJSON(mimeBytes)
}
@ -493,7 +501,12 @@ func GetAPSource(data []byte) Source {
// UnmarshalJSON
func (s *Source) UnmarshalJSON(data []byte) error {
*s = GetAPSource(data)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
*s = GetAPSource(val)
return nil
}

View file

@ -4,6 +4,8 @@ import (
"reflect"
"testing"
"time"
"github.com/valyala/fastjson"
)
func TestObjectNew(t *testing.T) {
@ -471,7 +473,10 @@ func TestSource_UnmarshalJSON(t *testing.T) {
func TestGetAPSource(t *testing.T) {
data := []byte(`{"source": {"content": "test", "mediaType": "text/plain" }}`)
a := GetAPSource(data)
par := fastjson.Parser{}
val, _ := par.ParseBytes(data)
a := GetAPSource(val)
if a.Content.First().String() != "test" {
t.Errorf("Content didn't match test value. Received %q, expecting %q", a.Content, "test")

View file

@ -5,6 +5,8 @@ import (
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// OrderedCollection is a subtype of Collection in which members of the logical
@ -229,7 +231,12 @@ func (o *OrderedCollection) Append(ob Item) error {
// UnmarshalJSON
func (o *OrderedCollection) UnmarshalJSON(data []byte) error {
return loadOrderedCollection(data, o)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadOrderedCollection(val, o)
}
// MarshalJSON

View file

@ -4,6 +4,8 @@ import (
"errors"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// OrderedCollectionPage type extends from both CollectionPage and OrderedCollection.
@ -191,7 +193,12 @@ func (o OrderedCollectionPage) Contains(r Item) bool {
// UnmarshalJSON
func (o *OrderedCollectionPage) UnmarshalJSON(data []byte) error {
return loadOrderedCollectionPage(data, o)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadOrderedCollectionPage(val, o)
}
// MarshalJSON

View file

@ -5,6 +5,8 @@ import (
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// Place represents a logical or physical location. See 5.3 Representing Places for additional information.
@ -153,7 +155,12 @@ func (p Place) GetID() ID {
// UnmarshalJSON
func (p *Place) UnmarshalJSON(data []byte) error {
return loadPlace(data, p)
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return loadPlace(val, p)
}
// MarshalJSON

View file

@ -5,6 +5,8 @@ import (
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// Profile a Profile is a content object that describes another Object,
@ -139,7 +141,12 @@ func (p Profile) GetID() ID {
// UnmarshalJSON
func (p *Profile) UnmarshalJSON(data []byte) error {
return loadProfile(data, p)
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return loadProfile(val, p)
}
// MarshalJSON

View file

@ -4,6 +4,8 @@ import (
"errors"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// Question represents a question being asked. Question objects are an extension of IntransitiveActivity.
@ -164,7 +166,12 @@ func (q Question) IsCollection() bool {
// UnmarshalJSON
func (q *Question) UnmarshalJSON(data []byte) error {
return loadQuestion(data, q)
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return loadQuestion(val, q)
}
func (q Question) MarshalJSON() ([]byte, error) {

View file

@ -5,6 +5,8 @@ import (
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// Relationship describes a relationship between two individuals.
@ -149,7 +151,12 @@ func (r Relationship) GetID() ID {
// UnmarshalJSON
func (r *Relationship) UnmarshalJSON(data []byte) error {
return loadRelationship(data, r)
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return loadRelationship(val, r)
}
// MarshalJSON

View file

@ -5,6 +5,8 @@ import (
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// Tombstone a Tombstone represents a content object that has been deleted.
@ -141,7 +143,12 @@ func (t Tombstone) GetID() ID {
// UnmarshalJSON
func (t *Tombstone) UnmarshalJSON(data []byte) error {
return loadTombstone(data, t)
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return loadTombstone(val, t)
}
// MarshalJSON