Improvements to jsonld.Marshal - now with tags
This commit is contained in:
parent
ca076e7185
commit
b2d595e31d
|
@ -9,19 +9,19 @@ import (
|
|||
type Ref string
|
||||
|
||||
type Context struct {
|
||||
URL Ref `jsonld:"_"`
|
||||
URL Ref `jsonld:"@url"`
|
||||
Language activitypub.NaturalLanguageValue `jsonld:"@language,omitempty,collapsible"`
|
||||
}
|
||||
|
||||
func (c *Context) Ref() Ref {
|
||||
return Ref(c.URL)
|
||||
}
|
||||
|
||||
func (r *Ref) MarshalText() ([]byte, error) {
|
||||
return []byte(*r), nil
|
||||
}
|
||||
|
||||
func (c *Context) MarshalJSON() ([]byte, error) {
|
||||
var a map[string]interface{}
|
||||
a = getMap(c)
|
||||
a = reflectToJsonLdMap(c)
|
||||
return json.Marshal(a)
|
||||
}
|
||||
|
|
|
@ -2,42 +2,54 @@ package jsonld
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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 {
|
||||
Context *Context `json:"@context"`
|
||||
Context *Context `jsonld:"@context,omitempty,collapsible"`
|
||||
Obj *interface{}
|
||||
}
|
||||
|
||||
func IsEmpty(v reflect.Value) bool {
|
||||
var ret bool
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
|
||||
ret = v.IsNil()
|
||||
case reflect.Slice, reflect.Map:
|
||||
ret = v.Len() == 0
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
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:
|
||||
ret = func(reflect.Value) bool {
|
||||
return func(reflect.Value) bool {
|
||||
var ret bool = true
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ret = ret && IsEmpty(v.Field(i))
|
||||
ret = ret && isEmptyValue(v.Field(i))
|
||||
}
|
||||
return ret
|
||||
}(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{})
|
||||
typ := reflect.TypeOf(v)
|
||||
val := reflect.ValueOf(v)
|
||||
|
@ -49,14 +61,22 @@ func getMap(v interface{}) map[string]interface{} {
|
|||
for i := 0; i < typ.NumField(); i++ {
|
||||
cField := typ.Field(i)
|
||||
cValue := val.Field(i)
|
||||
cTag := cField.Tag
|
||||
|
||||
jsonLdTag, ok := loadJsonLdTag(cTag)
|
||||
omitEmpty := ok && jsonLdTag.omitEmpty
|
||||
if jsonLdTag.ignore {
|
||||
continue
|
||||
}
|
||||
if cField.Anonymous {
|
||||
for k, v := range getMap(cValue.Interface()) {
|
||||
for k, v := range reflectToJsonLdMap(cValue.Interface()) {
|
||||
a[k] = v
|
||||
}
|
||||
} else {
|
||||
if !IsEmpty(cValue) {
|
||||
a[cField.Name] = cValue.Interface()
|
||||
}
|
||||
continue
|
||||
}
|
||||
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) {
|
||||
a := make(map[string]interface{})
|
||||
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) {
|
||||
a[k] = v
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(a)
|
||||
}
|
||||
|
||||
|
@ -80,7 +118,45 @@ func (p *payloadWithContext) UnmarshalJSON() {}
|
|||
|
||||
type Encoder struct{}
|
||||
|
||||
func Marshal(v interface{}, c *Context) ([]byte, error) {
|
||||
p := payloadWithContext{c, &v}
|
||||
return p.MarshalJSON()
|
||||
type jsonLdTag struct {
|
||||
name string
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type mockBase struct {
|
||||
|
@ -61,13 +61,13 @@ func TestMarshalNullContext(t *testing.T) {
|
|||
var a = struct {
|
||||
PropA string
|
||||
PropB float64
|
||||
} {"test", 0.0004}
|
||||
}{"test", 0.0004}
|
||||
|
||||
outL, errL := Marshal(a, nil )
|
||||
outL, errL := Marshal(a, nil)
|
||||
if errL != nil {
|
||||
t.Errorf("%s", errL)
|
||||
}
|
||||
outJ, errJ := Marshal(a, nil )
|
||||
outJ, errJ := Marshal(a, nil)
|
||||
if errJ != nil {
|
||||
t.Errorf("%s", errJ)
|
||||
}
|
||||
|
@ -78,34 +78,42 @@ func TestMarshalNullContext(t *testing.T) {
|
|||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
var a int = 0
|
||||
if !IsEmpty(reflect.ValueOf(a)) {
|
||||
t.Errorf("Invalid empty valid %s", a)
|
||||
if !isEmptyValue(reflect.ValueOf(a)) {
|
||||
t.Errorf("Invalid empty value %s", a)
|
||||
}
|
||||
if !IsEmpty(reflect.ValueOf(uint(a))) {
|
||||
t.Errorf("Invalid empty valid %s", uint(a))
|
||||
if !isEmptyValue(reflect.ValueOf(uint(a))) {
|
||||
t.Errorf("Invalid empty value %s", uint(a))
|
||||
}
|
||||
var b float64 = 0
|
||||
if !IsEmpty(reflect.ValueOf(b)) {
|
||||
t.Errorf("Invalid empty valid %s", b)
|
||||
if !isEmptyValue(reflect.ValueOf(b)) {
|
||||
t.Errorf("Invalid empty value %s", b)
|
||||
}
|
||||
var c string = ""
|
||||
if !IsEmpty(reflect.ValueOf(c)) {
|
||||
t.Errorf("Invalid empty valid %s", c)
|
||||
if !isEmptyValue(reflect.ValueOf(c)) {
|
||||
t.Errorf("Invalid empty value %s", c)
|
||||
}
|
||||
var d []byte = nil
|
||||
if !IsEmpty(reflect.ValueOf(d)) {
|
||||
t.Errorf("Invalid empty valid %v", d)
|
||||
if !isEmptyValue(reflect.ValueOf(d)) {
|
||||
t.Errorf("Invalid empty value %v", d)
|
||||
}
|
||||
var e *interface{} = nil
|
||||
if !IsEmpty(reflect.ValueOf(e)) {
|
||||
t.Errorf("Invalid empty valid %v", e)
|
||||
if !isEmptyValue(reflect.ValueOf(e)) {
|
||||
t.Errorf("Invalid empty value %v", e)
|
||||
}
|
||||
f := struct {
|
||||
a string
|
||||
b int
|
||||
}{}
|
||||
if !IsEmpty(reflect.ValueOf(f)) {
|
||||
t.Errorf("Invalid empty valid %v", f)
|
||||
if !isEmptyValue(reflect.ValueOf(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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"activitypub"
|
||||
"jsonld"
|
||||
"testing"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TestAcceptSerialization(t *testing.T) {
|
||||
o := activitypub.AcceptNew("https://localhost/myactivity")
|
||||
o.Name = make(activitypub.NaturalLanguageValue, 1)
|
||||
o.Name["en"] = "test"
|
||||
obj := activitypub.AcceptNew("https://localhost/myactivity")
|
||||
obj.Name = make(activitypub.NaturalLanguageValue, 1)
|
||||
obj.Name["en"] = "test"
|
||||
|
||||
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 {
|
||||
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) {
|
||||
id := activitypub.ObjectId("test_object")
|
||||
o := activitypub.AcceptNew(id)
|
||||
o.Name["en"] = "Accept New"
|
||||
obj := activitypub.AcceptNew(id)
|
||||
obj.Name["en"] = "Accept New"
|
||||
|
||||
baseUri := string(activitypub.ActivityBaseURI)
|
||||
c := jsonld.Context{
|
||||
URL: jsonld.Ref(baseUri + string(o.Type)),
|
||||
ctx := jsonld.Context{
|
||||
URL: jsonld.Ref(baseUri + string(obj.Type)),
|
||||
}
|
||||
|
||||
out, err := jsonld.Marshal(o, &c)
|
||||
data, err := jsonld.Marshal(obj, &ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
outNoC, errNoC := jsonld.Marshal(o, nil)
|
||||
if errNoC != nil {
|
||||
t.Error(errNoC)
|
||||
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)
|
||||
}
|
||||
t.Logf("%s\n\n%s", out, outNoC)
|
||||
}
|
||||
|
|
Reference in a new issue