Added basic unmarshalling for objects with jsonld context
This commit is contained in:
parent
1997a54824
commit
175fd5fdeb
4 changed files with 216 additions and 17 deletions
|
@ -2,8 +2,10 @@ package jsonld
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"activitypub"
|
ap "activitypub"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ref basic type
|
// Ref basic type
|
||||||
|
@ -13,8 +15,8 @@ type Ref string
|
||||||
// Terms are case sensitive and any valid string that is not a reserved JSON-LD
|
// Terms are case sensitive and any valid string that is not a reserved JSON-LD
|
||||||
// keyword can be used as a term.
|
// keyword can be used as a term.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
URL Ref `jsonld:"@url"`
|
URL Ref `jsonld:"@url"`
|
||||||
Language activitypub.NaturalLanguageValue `jsonld:"@language,omitempty,collapsible"`
|
Language ap.LangRef `jsonld:"@language,omitempty,collapsible"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collapsible is an interface used by the JSON-LD marshaller to collapse a struct to one single value
|
// Collapsible is an interface used by the JSON-LD marshaller to collapse a struct to one single value
|
||||||
|
@ -45,3 +47,46 @@ func (c *Context) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
return json.Marshal(a.object)
|
return json.Marshal(a.object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON tries to load the Context from the incoming json value
|
||||||
|
func (c *Context) UnmarshalJSON(data []byte) error {
|
||||||
|
if data[0] == '"' {
|
||||||
|
// a quoted string - loading it to c.URL
|
||||||
|
if data[len(data)-1] != '"' {
|
||||||
|
return fmt.Errorf("invalid string value when unmarshalling Context value")
|
||||||
|
}
|
||||||
|
c.URL = Ref(data[1 : len(data)-1])
|
||||||
|
}
|
||||||
|
if data[0] == '{' {
|
||||||
|
// an object - trying to load it to the struct
|
||||||
|
if data[len(data)-1] != '}' {
|
||||||
|
return fmt.Errorf("invalid object value when unmarshalling Context value")
|
||||||
|
}
|
||||||
|
|
||||||
|
var s scanner
|
||||||
|
s.reset()
|
||||||
|
var d decodeState
|
||||||
|
d.scan = s
|
||||||
|
d.init(data)
|
||||||
|
|
||||||
|
d.scan.reset()
|
||||||
|
|
||||||
|
t := d.valueInterface()
|
||||||
|
|
||||||
|
for key, value := range t.(map[string]interface{}) {
|
||||||
|
switch strings.ToLower(key) {
|
||||||
|
case "@url":
|
||||||
|
fallthrough
|
||||||
|
case "url":
|
||||||
|
c.URL = Ref(value.(string))
|
||||||
|
case "@language":
|
||||||
|
fallthrough
|
||||||
|
case "language":
|
||||||
|
c.Language = ap.LangRef(value.(string))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unkown Context field %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jsonld
|
package jsonld
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"activitypub"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -32,8 +31,7 @@ func TestContext_Ref(t *testing.T) {
|
||||||
func TestContext_MarshalJSON(t *testing.T) {
|
func TestContext_MarshalJSON(t *testing.T) {
|
||||||
url := "test"
|
url := "test"
|
||||||
c := Context{URL: Ref(url)}
|
c := Context{URL: Ref(url)}
|
||||||
c.Language = make(activitypub.NaturalLanguageValue, 1)
|
c.Language = "en-GB"
|
||||||
c.Language["en"] = "en-GB"
|
|
||||||
|
|
||||||
out, err := c.MarshalJSON()
|
out, err := c.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -14,10 +14,6 @@ import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *payloadWithContext) UnmarshalJSON([]byte) error {
|
|
||||||
return fmt.Errorf("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal parses the JSON-encoded data and stores the result
|
// Unmarshal parses the JSON-encoded data and stores the result
|
||||||
// in the value pointed to by v. If v is nil or not a pointer,
|
// in the value pointed to by v. If v is nil or not a pointer,
|
||||||
// Unmarshal returns an InvalidUnmarshalError.
|
// Unmarshal returns an InvalidUnmarshalError.
|
||||||
|
@ -91,17 +87,15 @@ func (p *payloadWithContext) UnmarshalJSON([]byte) error {
|
||||||
// character U+FFFD.
|
// character U+FFFD.
|
||||||
//
|
//
|
||||||
func Unmarshal(data []byte, v interface{}) error {
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
// Check for well-formedness.
|
|
||||||
// Avoids filling out half a data structure
|
|
||||||
// before discovering a JSON syntax error.
|
|
||||||
var d decodeState
|
var d decodeState
|
||||||
|
|
||||||
err := checkValid(data, &d.scan)
|
err := checkValid(data, &d.scan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.init(data)
|
d.init(data)
|
||||||
return d.unmarshal(v)
|
return d.unmarshal(&v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshaler is the interface implemented by types
|
// Unmarshaler is the interface implemented by types
|
||||||
|
|
|
@ -1,7 +1,169 @@
|
||||||
package jsonld
|
package jsonld
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
func TestPayloadWithContext_UnmarshalJSON(t *testing.T) {
|
ap "activitypub"
|
||||||
t.Skipf("Not implemented")
|
)
|
||||||
|
|
||||||
|
func TestUnmarshalWithEmptyJsonObject(t *testing.T) {
|
||||||
|
obj := mockTypeA{}
|
||||||
|
err := Unmarshal([]byte("{}"), &obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if obj.Id != "" {
|
||||||
|
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||||
|
}
|
||||||
|
if obj.Name != "" {
|
||||||
|
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||||
|
}
|
||||||
|
if obj.Type != "" {
|
||||||
|
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||||
|
}
|
||||||
|
if obj.PropA != "" {
|
||||||
|
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||||
|
}
|
||||||
|
if obj.PropB != 0 {
|
||||||
|
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockWithContext struct {
|
||||||
|
mockTypeA
|
||||||
|
Context Context `jsonld:"@context"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalWithEmptyJsonObjectWithStringContext(t *testing.T) {
|
||||||
|
obj := mockWithContext{}
|
||||||
|
url := "http://www.habarnam.ro"
|
||||||
|
data := []byte(`{"@context": "` + url + `" }`)
|
||||||
|
err := Unmarshal(data, &obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if obj.Context.Ref() != Ref(url) {
|
||||||
|
t.Errorf("@context should have been %q, found %q", url, obj.Context.Collapse())
|
||||||
|
}
|
||||||
|
if obj.Id != "" {
|
||||||
|
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||||
|
}
|
||||||
|
if obj.Name != "" {
|
||||||
|
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||||
|
}
|
||||||
|
if obj.Type != "" {
|
||||||
|
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||||
|
}
|
||||||
|
if obj.PropA != "" {
|
||||||
|
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||||
|
}
|
||||||
|
if obj.PropB != 0 {
|
||||||
|
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalWithEmptyJsonObjectWithObjectContext(t *testing.T) {
|
||||||
|
obj := mockWithContext{}
|
||||||
|
url := "http://www.habarnam.ro"
|
||||||
|
data := []byte(`{"@context": { "@url": "` + url + `"} }`)
|
||||||
|
err := Unmarshal(data, &obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if obj.Context.Ref() != Ref(url) {
|
||||||
|
t.Errorf("@context should have been %q, found %q", url, obj.Context.Collapse())
|
||||||
|
}
|
||||||
|
if obj.Id != "" {
|
||||||
|
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||||
|
}
|
||||||
|
if obj.Name != "" {
|
||||||
|
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||||
|
}
|
||||||
|
if obj.Type != "" {
|
||||||
|
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||||
|
}
|
||||||
|
if obj.PropA != "" {
|
||||||
|
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||||
|
}
|
||||||
|
if obj.PropB != 0 {
|
||||||
|
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalWithEmptyJsonObjectWithOneLanguageContext(t *testing.T) {
|
||||||
|
obj := mockWithContext{}
|
||||||
|
url := "http://www.habarnam.ro"
|
||||||
|
langEn := "en-US"
|
||||||
|
|
||||||
|
data := []byte(`{"@context": { "@url": "` + url + `", "@language": "` + langEn + `"} }`)
|
||||||
|
err := Unmarshal(data, &obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if obj.Context.Ref() != Ref(url) {
|
||||||
|
t.Errorf("@context should have been %q, found %q", url, obj.Context.Collapse())
|
||||||
|
}
|
||||||
|
if obj.Context.Language != ap.LangRef(langEn) {
|
||||||
|
t.Errorf("@context should have been %q, found %q", url, obj.Context.Collapse())
|
||||||
|
}
|
||||||
|
if obj.Id != "" {
|
||||||
|
t.Errorf("Id should have been an empty string, found %s", obj.Id)
|
||||||
|
}
|
||||||
|
if obj.Name != "" {
|
||||||
|
t.Errorf("Name should have been an empty string, found %s", obj.Name)
|
||||||
|
}
|
||||||
|
if obj.Type != "" {
|
||||||
|
t.Errorf("Type should have been an empty string, found %s", obj.Type)
|
||||||
|
}
|
||||||
|
if obj.PropA != "" {
|
||||||
|
t.Errorf("PropA should have been an empty string, found %s", obj.PropA)
|
||||||
|
}
|
||||||
|
if obj.PropB != 0 {
|
||||||
|
t.Errorf("PropB should have been 0.0, found %f", obj.PropB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestUnmarshalWithEmptyJsonObjectWithFullObject(t *testing.T) {
|
||||||
|
obj := mockWithContext{}
|
||||||
|
url := "http://www.habarnam.ro"
|
||||||
|
langEn := "en-US"
|
||||||
|
propA := "ana"
|
||||||
|
var propB float32 = 6.66
|
||||||
|
typ := "test"
|
||||||
|
name := "test object #1"
|
||||||
|
id := "777sdad"
|
||||||
|
|
||||||
|
data := []byte(`{
|
||||||
|
"@context": { "@url": "` + url + `", "@language": "` + langEn + `"},
|
||||||
|
"PropA": "` + propA + `",
|
||||||
|
"PropB": ` + strconv.FormatFloat(float64(propB), 'f', 2, 32) + `,
|
||||||
|
"Id" : "` + id + `",
|
||||||
|
"Name" : "` + name + `",
|
||||||
|
"Type" : "` + typ + `"
|
||||||
|
}`)
|
||||||
|
err := Unmarshal(data, &obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if obj.Context.Ref() != Ref(url) {
|
||||||
|
t.Errorf("@context should have been %q, found %q", url, obj.Context.Collapse())
|
||||||
|
}
|
||||||
|
if obj.Context.Language != ap.LangRef(langEn) {
|
||||||
|
t.Errorf("@context should have been %q, found %q", url, obj.Context.Collapse())
|
||||||
|
}
|
||||||
|
if obj.Id != id {
|
||||||
|
t.Errorf("Id should have been %q, found %q", id, obj.Id)
|
||||||
|
}
|
||||||
|
if obj.Name != name {
|
||||||
|
t.Errorf("Name should have been %q, found %q", name, obj.Name)
|
||||||
|
}
|
||||||
|
if obj.Type != typ {
|
||||||
|
t.Errorf("Type should have been %q, found %q", typ, obj.Type)
|
||||||
|
}
|
||||||
|
if obj.PropA != propA {
|
||||||
|
t.Errorf("PropA should have been %q, found %q", propA, obj.PropA)
|
||||||
|
}
|
||||||
|
if obj.PropB != propB {
|
||||||
|
t.Errorf("PropB should have been %f, found %f", propB, obj.PropB)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue