Improvements to jsonld.Marshal - now with tags
This commit is contained in:
parent
ca076e7185
commit
b2d595e31d
4 changed files with 172 additions and 69 deletions
|
@ -9,19 +9,19 @@ import (
|
||||||
type Ref string
|
type Ref string
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
URL Ref `jsonld:"_"`
|
URL Ref `jsonld:"@url"`
|
||||||
Language activitypub.NaturalLanguageValue `jsonld:"@language,omitempty,collapsible"`
|
Language activitypub.NaturalLanguageValue `jsonld:"@language,omitempty,collapsible"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Ref() Ref {
|
func (c *Context) Ref() Ref {
|
||||||
return Ref(c.URL)
|
return Ref(c.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Ref) MarshalText() ([]byte, error) {
|
func (r *Ref) MarshalText() ([]byte, error) {
|
||||||
return []byte(*r), nil
|
return []byte(*r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) MarshalJSON() ([]byte, error) {
|
func (c *Context) MarshalJSON() ([]byte, error) {
|
||||||
var a map[string]interface{}
|
var a map[string]interface{}
|
||||||
a = getMap(c)
|
a = reflectToJsonLdMap(c)
|
||||||
return json.Marshal(a)
|
return json.Marshal(a)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,42 +2,54 @@ package jsonld
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tagLabel = "jsonld"
|
||||||
|
tagOmitEmpty = "omitempty"
|
||||||
|
tagCollapsible = "collapsible"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Marshal(v interface{}, c *Context) ([]byte, error) {
|
||||||
|
p := payloadWithContext{c, &v}
|
||||||
|
return p.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
type payloadWithContext struct {
|
type payloadWithContext struct {
|
||||||
Context *Context `json:"@context"`
|
Context *Context `jsonld:"@context,omitempty,collapsible"`
|
||||||
Obj *interface{}
|
Obj *interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsEmpty(v reflect.Value) bool {
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
var ret bool
|
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
ret = v.IsNil()
|
return v.Len() == 0
|
||||||
case reflect.Slice, reflect.Map:
|
case reflect.Bool:
|
||||||
ret = v.Len() == 0
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
ret = func(reflect.Value) bool {
|
return func(reflect.Value) bool {
|
||||||
var ret bool = true
|
var ret bool = true
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
ret = ret && IsEmpty(v.Field(i))
|
ret = ret && isEmptyValue(v.Field(i))
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}(v)
|
}(v)
|
||||||
case reflect.String:
|
|
||||||
ret = v.String() == ""
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
ret = v.Int() == 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
ret = v.Uint() == 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
ret = v.Float() == 0.0
|
|
||||||
}
|
}
|
||||||
return ret
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMap(v interface{}) map[string]interface{} {
|
func reflectToJsonLdMap(v interface{}) map[string]interface{} {
|
||||||
a := make(map[string]interface{})
|
a := make(map[string]interface{})
|
||||||
typ := reflect.TypeOf(v)
|
typ := reflect.TypeOf(v)
|
||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
|
@ -49,14 +61,22 @@ func getMap(v interface{}) map[string]interface{} {
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
cField := typ.Field(i)
|
cField := typ.Field(i)
|
||||||
cValue := val.Field(i)
|
cValue := val.Field(i)
|
||||||
|
cTag := cField.Tag
|
||||||
|
|
||||||
|
jsonLdTag, ok := loadJsonLdTag(cTag)
|
||||||
|
omitEmpty := ok && jsonLdTag.omitEmpty
|
||||||
|
if jsonLdTag.ignore {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if cField.Anonymous {
|
if cField.Anonymous {
|
||||||
for k, v := range getMap(cValue.Interface()) {
|
for k, v := range reflectToJsonLdMap(cValue.Interface()) {
|
||||||
a[k] = v
|
a[k] = v
|
||||||
}
|
}
|
||||||
} else {
|
continue
|
||||||
if !IsEmpty(cValue) {
|
|
||||||
a[cField.Name] = cValue.Interface()
|
|
||||||
}
|
}
|
||||||
|
empty := isEmptyValue(cValue)
|
||||||
|
if !empty || empty && !omitEmpty {
|
||||||
|
a[jsonLdName(cField.Name, jsonLdTag)] = cValue.Interface()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,13 +86,31 @@ func getMap(v interface{}) map[string]interface{} {
|
||||||
func (p *payloadWithContext) MarshalJSON() ([]byte, error) {
|
func (p *payloadWithContext) MarshalJSON() ([]byte, error) {
|
||||||
a := make(map[string]interface{})
|
a := make(map[string]interface{})
|
||||||
if p.Context != nil {
|
if p.Context != nil {
|
||||||
a["@context"] = p.Context.Ref()
|
typ := reflect.TypeOf(*p)
|
||||||
}
|
cMirror, _ := typ.FieldByName("Context")
|
||||||
|
jsonLdTag, ok := loadJsonLdTag(cMirror.Tag)
|
||||||
|
omitEmpty := ok && jsonLdTag.omitEmpty
|
||||||
|
collapsible := ok && jsonLdTag.collapsible
|
||||||
|
|
||||||
for k, v := range getMap(*p.Obj) {
|
con := reflectToJsonLdMap(p.Context)
|
||||||
|
if len(con) > 0 || !omitEmpty {
|
||||||
|
for _, v := range con {
|
||||||
|
a[jsonLdName(cMirror.Name, jsonLdTag)] = v
|
||||||
|
if len(con) == 1 && collapsible {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *p.Obj != nil {
|
||||||
|
oMap := reflectToJsonLdMap(*p.Obj)
|
||||||
|
if len(oMap) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid object to marshall")
|
||||||
|
}
|
||||||
|
for k, v := range oMap {
|
||||||
a[k] = v
|
a[k] = v
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return json.Marshal(a)
|
return json.Marshal(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +118,45 @@ func (p *payloadWithContext) UnmarshalJSON() {}
|
||||||
|
|
||||||
type Encoder struct{}
|
type Encoder struct{}
|
||||||
|
|
||||||
func Marshal(v interface{}, c *Context) ([]byte, error) {
|
type jsonLdTag struct {
|
||||||
p := payloadWithContext{c, &v}
|
name string
|
||||||
return p.MarshalJSON()
|
ignore bool
|
||||||
|
omitEmpty bool
|
||||||
|
collapsible bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadJsonLdTag(tag reflect.StructTag) (jsonLdTag, bool) {
|
||||||
|
jlTag, ok := tag.Lookup(tagLabel)
|
||||||
|
if !ok {
|
||||||
|
return jsonLdTag{}, false
|
||||||
|
}
|
||||||
|
val := strings.Split(jlTag, ",")
|
||||||
|
cont := func(arr []string, s string) bool {
|
||||||
|
for _, v := range arr {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t := jsonLdTag{
|
||||||
|
omitEmpty: cont(val, tagOmitEmpty),
|
||||||
|
collapsible: cont(val, tagCollapsible),
|
||||||
|
}
|
||||||
|
t.name, t.ignore = func(v string) (string, bool) {
|
||||||
|
if len(v) > 0 && v != "_" {
|
||||||
|
return v, false
|
||||||
|
} else {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
}(val[0])
|
||||||
|
|
||||||
|
return t, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonLdName(n string, tag jsonLdTag) string {
|
||||||
|
if len(tag.name) > 0 {
|
||||||
|
return tag.name
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package jsonld
|
package jsonld
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"bytes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockBase struct {
|
type mockBase struct {
|
||||||
|
@ -61,13 +61,13 @@ func TestMarshalNullContext(t *testing.T) {
|
||||||
var a = struct {
|
var a = struct {
|
||||||
PropA string
|
PropA string
|
||||||
PropB float64
|
PropB float64
|
||||||
} {"test", 0.0004}
|
}{"test", 0.0004}
|
||||||
|
|
||||||
outL, errL := Marshal(a, nil )
|
outL, errL := Marshal(a, nil)
|
||||||
if errL != nil {
|
if errL != nil {
|
||||||
t.Errorf("%s", errL)
|
t.Errorf("%s", errL)
|
||||||
}
|
}
|
||||||
outJ, errJ := Marshal(a, nil )
|
outJ, errJ := Marshal(a, nil)
|
||||||
if errJ != nil {
|
if errJ != nil {
|
||||||
t.Errorf("%s", errJ)
|
t.Errorf("%s", errJ)
|
||||||
}
|
}
|
||||||
|
@ -78,34 +78,42 @@ func TestMarshalNullContext(t *testing.T) {
|
||||||
|
|
||||||
func TestIsEmpty(t *testing.T) {
|
func TestIsEmpty(t *testing.T) {
|
||||||
var a int = 0
|
var a int = 0
|
||||||
if !IsEmpty(reflect.ValueOf(a)) {
|
if !isEmptyValue(reflect.ValueOf(a)) {
|
||||||
t.Errorf("Invalid empty valid %s", a)
|
t.Errorf("Invalid empty value %s", a)
|
||||||
}
|
}
|
||||||
if !IsEmpty(reflect.ValueOf(uint(a))) {
|
if !isEmptyValue(reflect.ValueOf(uint(a))) {
|
||||||
t.Errorf("Invalid empty valid %s", uint(a))
|
t.Errorf("Invalid empty value %s", uint(a))
|
||||||
}
|
}
|
||||||
var b float64 = 0
|
var b float64 = 0
|
||||||
if !IsEmpty(reflect.ValueOf(b)) {
|
if !isEmptyValue(reflect.ValueOf(b)) {
|
||||||
t.Errorf("Invalid empty valid %s", b)
|
t.Errorf("Invalid empty value %s", b)
|
||||||
}
|
}
|
||||||
var c string = ""
|
var c string = ""
|
||||||
if !IsEmpty(reflect.ValueOf(c)) {
|
if !isEmptyValue(reflect.ValueOf(c)) {
|
||||||
t.Errorf("Invalid empty valid %s", c)
|
t.Errorf("Invalid empty value %s", c)
|
||||||
}
|
}
|
||||||
var d []byte = nil
|
var d []byte = nil
|
||||||
if !IsEmpty(reflect.ValueOf(d)) {
|
if !isEmptyValue(reflect.ValueOf(d)) {
|
||||||
t.Errorf("Invalid empty valid %v", d)
|
t.Errorf("Invalid empty value %v", d)
|
||||||
}
|
}
|
||||||
var e *interface{} = nil
|
var e *interface{} = nil
|
||||||
if !IsEmpty(reflect.ValueOf(e)) {
|
if !isEmptyValue(reflect.ValueOf(e)) {
|
||||||
t.Errorf("Invalid empty valid %v", e)
|
t.Errorf("Invalid empty value %v", e)
|
||||||
}
|
}
|
||||||
f := struct {
|
f := struct {
|
||||||
a string
|
a string
|
||||||
b int
|
b int
|
||||||
}{}
|
}{}
|
||||||
if !IsEmpty(reflect.ValueOf(f)) {
|
if !isEmptyValue(reflect.ValueOf(f)) {
|
||||||
t.Errorf("Invalid empty valid %v", f)
|
t.Errorf("Invalid empty value %v", f)
|
||||||
|
}
|
||||||
|
g := false
|
||||||
|
if !isEmptyValue(reflect.ValueOf(g)) {
|
||||||
|
t.Errorf("Invalid empty value %v", g)
|
||||||
|
}
|
||||||
|
h := true
|
||||||
|
if isEmptyValue(reflect.ValueOf(h)) {
|
||||||
|
t.Errorf("Invalid empty value %v", h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,61 @@
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"activitypub"
|
"activitypub"
|
||||||
"jsonld"
|
"jsonld"
|
||||||
"testing"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAcceptSerialization(t *testing.T) {
|
func TestAcceptSerialization(t *testing.T) {
|
||||||
o := activitypub.AcceptNew("https://localhost/myactivity")
|
obj := activitypub.AcceptNew("https://localhost/myactivity")
|
||||||
o.Name = make(activitypub.NaturalLanguageValue, 1)
|
obj.Name = make(activitypub.NaturalLanguageValue, 1)
|
||||||
o.Name["en"] = "test"
|
obj.Name["en"] = "test"
|
||||||
|
|
||||||
ctx := jsonld.Context{URL: "https://www.w3.org/ns/activitystreams"}
|
ctx := jsonld.Context{URL: "https://www.w3.org/ns/activitystreams"}
|
||||||
|
|
||||||
bytes, err := jsonld.Marshal(o, &ctx)
|
data, err := jsonld.Marshal(obj, &ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error: %v", err)
|
t.Errorf("Error: %v", err)
|
||||||
}
|
}
|
||||||
t.Logf("%s", bytes)
|
if !strings.Contains(string(data), string(ctx.URL)) {
|
||||||
|
t.Errorf("Could not find context url %#v in output %s", ctx.URL, data)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), string(obj.Id)) {
|
||||||
|
t.Errorf("Could not find id %#v in output %s", string(obj.Id), data)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), string(obj.Name["en"])) {
|
||||||
|
t.Errorf("Could not find name %#v in output %s", string(obj.Name["en"]), data)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), string(obj.Type)) {
|
||||||
|
t.Errorf("Could not find activity type %#v in output %s", obj.Type, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateActivityHTTPSerialization(t *testing.T) {
|
func TestCreateActivityHTTPSerialization(t *testing.T) {
|
||||||
id := activitypub.ObjectId("test_object")
|
id := activitypub.ObjectId("test_object")
|
||||||
o := activitypub.AcceptNew(id)
|
obj := activitypub.AcceptNew(id)
|
||||||
o.Name["en"] = "Accept New"
|
obj.Name["en"] = "Accept New"
|
||||||
|
|
||||||
baseUri := string(activitypub.ActivityBaseURI)
|
baseUri := string(activitypub.ActivityBaseURI)
|
||||||
c := jsonld.Context{
|
ctx := jsonld.Context{
|
||||||
URL: jsonld.Ref(baseUri + string(o.Type)),
|
URL: jsonld.Ref(baseUri + string(obj.Type)),
|
||||||
}
|
}
|
||||||
|
data, err := jsonld.Marshal(obj, &ctx)
|
||||||
out, err := jsonld.Marshal(o, &c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
outNoC, errNoC := jsonld.Marshal(o, nil)
|
if !strings.Contains(string(data), string(ctx.URL)) {
|
||||||
if errNoC != nil {
|
t.Errorf("Could not find context url %#v in output %s", ctx.URL, data)
|
||||||
t.Error(errNoC)
|
}
|
||||||
|
if !strings.Contains(string(data), string(obj.Id)) {
|
||||||
|
t.Errorf("Could not find id %#v in output %s", string(obj.Id), data)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), string(obj.Name["en"])) {
|
||||||
|
t.Errorf("Could not find name %#v in output %s", string(obj.Name["en"]), data)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), string(obj.Type)) {
|
||||||
|
t.Errorf("Could not find activity type %#v in output %s", obj.Type, data)
|
||||||
}
|
}
|
||||||
t.Logf("%s\n\n%s", out, outNoC)
|
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue