Improvements to jsonld.Marshal - now with tags

This commit is contained in:
Marius Orcsik 2017-10-02 16:19:23 +02:00
parent ca076e7185
commit b2d595e31d
No known key found for this signature in database
GPG key ID: C36D1EBE93A6EEAE
4 changed files with 172 additions and 69 deletions

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}