Changes
6 changed files (+302/-290)
-
-
@@ -2,44 +2,137 @@ package jsonldimport ( "encoding/json" "fmt" "strings" ) var Ctx interface{} // From the JSON-LD spec 3.3 // https://www.w3.org/TR/json-ld/#dfn-keyword const ( // @context // Used to define the short-hand names that are used throughout a JSON-LD document. // These short-hand names are called terms and help developers to express specific identifiers in a compact manner. // The @context keyword is described in detail in section 5.1 The Context. ContextKw Term = "@context" // @id //Used to uniquely identify things that are being described in the document with IRIs or blank node identifiers. // This keyword is described in section 5.3 Node Identifiers. IdKw Term = "@id" // @value // Used to specify the data that is associated with a particular property in the graph. // This keyword is described in section 6.9 String Internationalization and section 6.4 Typed Values. ValueKw Term = "@value" // @language // Used to specify the language for a particular string value or the default language of a JSON-LD document. // This keyword is described in section 6.9 String Internationalization. LanguageKw Term = "@language" //@type //Used to set the data type of a node or typed value. This keyword is described in section 6.4 Typed Values. TypeKw Term = "@type" // @container // Used to set the default container type for a term. This keyword is described in section 6.11 Sets and Lists. ContainerKw Term = "@container" //@list //Used to express an ordered set of data. This keyword is described in section 6.11 Sets and Lists. ListKw Term = "@list" // @set // Used to express an unordered set of data and to ensure that values are always represented as arrays. // This keyword is described in section 6.11 Sets and Lists. SetKw Term = "@set" // @reverse // Used to express reverse properties. This keyword is described in section 6.12 Reverse Properties. ReverseKw Term = "@reverse" // @index // Used to specify that a container is used to index information and that processing should continue deeper // into a JSON data structure. This keyword is described in section 6.16 Data Indexing. IndexKw Term = "@index" // @base // Used to set the base IRI against which relative IRIs are resolved. T // his keyword is described in section 6.1 Base IRI. BaseKw Term = "@base" // @vocab // Used to expand properties and values in @type with a common prefix IRI. // This keyword is described in section 6.2 Default Vocabulary. VocabKw Term = "@vocab" // @graph // Used to express a graph. This keyword is described in section 6.13 Named Graphs. GraphKw Term = "@graph" ) // Ref basic type type ( Ref string LangRef string Term string IRI string Terms []Term ) // Context is the basic JSON-LD element. It is used to map terms to IRIs. type IRILike interface { IsCompact() bool IsAbsolute() bool IsRelative() bool } func (i IRI) IsCompact() bool { return !i.IsAbsolute() && strings.Contains(string(i), ":") } func (i IRI) IsAbsolute() bool { return strings.Contains(string(i), "https://") } func (i IRI) IsRelative() bool { return !i.IsAbsolute() } var keywords = Terms{ BaseKw, ContextKw, ContainerKw, GraphKw, IdKw, IndexKw, LanguageKw, ListKw, ReverseKw, SetKw, TypeKw, ValueKw, VocabKw, } type ContextObject struct { ID interface{} `jsonld:"@id,omitempty,collapsible"` Type interface{} `jsonld:"@type,omitempty,collapsible"` } // Context is the basic JSON-LD element. It is used to map terms to IRIs or JSON objects. // Terms are case sensitive and any valid string that is not a reserved JSON-LD // keyword can be used as a term. type Context struct { URL Ref `jsonld:"@url,omitempty,collapsible"` Language LangRef `jsonld:"@language,omitempty,collapsible"` } type Context map[Term]IRI //type Context Collapsible // Collapsible is an interface used by the JSON-LD marshaller to collapse a struct to one single value type Collapsible interface { Collapse() []byte Collapse() interface{} } // Ref returns a new Ref object based on Context URL func (c *Context) Ref() Ref { return Ref(c.URL) // Collapse returns the plain text collapsed value of the current Context object func (c *Context) Collapse() interface{} { if len(*c) == 1 { for _, iri := range *c { return iri } } return c } // Collapse returns the plain text collapsed value of the current Context object func (c *Context) Collapse() []byte { return []byte(c.URL) // Collapse returns the plain text collapsed value of the current IRI string func (i IRI) Collapse() interface{} { return i } // MarshalText basic stringify function func (r *Ref) MarshalText() ([]byte, error) { return []byte(*r), nil func (i IRI) MarshalText() ([]byte, error) { return []byte(i), nil } // MarshalJSON returns the JSON document represented by the current Context
-
@@ -53,43 +146,5 @@ func (c *Context) MarshalJSON() ([]byte, error) {// 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 = LangRef(value.(string)) default: return fmt.Errorf("unkown Context field %q", key) } } } return nil }
-
-
-
@@ -8,7 +8,7 @@ import (func TestRef_MarshalText(t *testing.T) { test := "test" a := Ref(test) a := IRI(test) out, err := a.MarshalText() if err != nil {
-
@@ -19,19 +19,10 @@ func TestRef_MarshalText(t *testing.T) {} } func TestContext_Ref(t *testing.T) { url := "test" c := Context{URL: Ref(url)} if c.Ref() != Ref(url) { t.Errorf("Invalid result %#v, expected %#v", c.Ref(), Ref(url)) } } func TestContext_MarshalJSON(t *testing.T) { url := "test" c := Context{URL: Ref(url)} c.Language = "en-GB" c := Context{"_": IRI(url)} //c.Language = "en-GB" out, err := c.MarshalJSON() if err != nil {
-
-
-
@@ -1,7 +1,6 @@package jsonld import ( "strconv" "testing" )
-
@@ -33,135 +32,135 @@ type mockWithContext struct {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 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 TestUnmarshalWithEmptyJsonObjectWithOneLanguageContext(t *testing.T) { obj := mockWithContext{} url := "http://www.habarnam.ro" langEn := "en-US" //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) // } //} 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 != 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 != 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) } } //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 != 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 != 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) // } //}
-
-
-
@@ -28,6 +28,8 @@ import ("unicode/utf8" ) var Ctxt Context const ( tagLabel = "jsonld" tagOmitEmpty = "omitempty"
-
@@ -35,7 +37,9 @@ const () type payloadWithContext struct { Context interface{} `jsonld:"@context,collapsible"` Context Collapsible `jsonld:"@context,omitempty,collapsible"` ID interface{} `jsonld:"@id,omitempty,collapsible"` Type interface{} `jsonld:"@type,omitempty,collapsible"` Obj interface{} }
-
@@ -108,48 +112,21 @@ func reflectToJSONValue(v interface{}) jsonCapableValue {return a } func (p *payloadWithContext) MarshalJSON() ([]byte, error) { a := jsonCapableValue{} a.isScalar = true a.object = make(map[string]interface{}) if p.Context != nil { a.isScalar = false typ := reflect.TypeOf(*p) cMirror, _ := typ.FieldByName("Context") jsonLdTag, ok := LoadJSONLdTag(cMirror.Tag) omitEmpty := ok && jsonLdTag.OmitEmpty collapsible := ok && jsonLdTag.Collapsible con := reflectToJSONValue(p.Context) if len(con.object) > 0 || !omitEmpty { for _, v := range con.object { a.object[JSONLdName(cMirror.Name, jsonLdTag)] = v if len(con.object) == 1 && collapsible { break } } } } if p.Obj != nil { oMap := reflectToJSONValue(p.Obj) if oMap.isScalar && a.isScalar { a.isScalar = true a.scalar = oMap.scalar } else { if len(oMap.object) == 0 { return nil, fmt.Errorf("invalid object to marshall") } a.isScalar = false func (p payloadWithContext) Collapse() interface{} { return p } for k, v := range oMap.object { a.object[k] = v } } // WithContext func WithContext(c Collapsible) payloadWithContext { return payloadWithContext{ Context: c, } if a.isScalar { return json.Marshal(a.scalar) } return json.Marshal(a.object) } // Marshal func (p payloadWithContext) Marshal(v interface{}) ([]byte, error) { p.Obj = v return Marshal(p) } type Tag struct {
-
@@ -333,28 +310,14 @@ type UnsupportedTypeError struct {func Marshal(v interface{}) ([]byte, error) { e := &encodeState{} if Ctx != nil { a := payloadWithContext{ Context: Ctx, Obj: nil, } //errC := e.marshal(ctx, encOpts{escapeHTML: true}) //if errC != nil { // return nil, errC //} err := e.marshal(a, encOpts{escapeHTML: true}) if err != nil { return nil, err } } err := e.marshal(v, encOpts{escapeHTML: true}) if err != nil { return nil, err } output := e.Bytes() return bytes.Replace(output, []byte(`,"Obj":null}{`), []byte(","), 1), nil // @todo(marius): fix this ugly hack output := bytes.Replace(e.Bytes(), []byte(`,"Obj":{`), []byte(","), 1) return output[:len(output)-1], nil } func (e *UnsupportedTypeError) Error() string {
-
-
-
@@ -24,35 +24,36 @@ func TestMarshal(t *testing.T) {b := mockTypeA{} url := "http://www.habarnam.ro" Ctx = &Context{URL: Ref(url)} p := WithContext(IRI(url)) var err error var out []byte out, err = Marshal(a) out, err = p.Marshal(a) if err != nil { t.Errorf("%s", err) } if !strings.Contains(string(out), "@context") { t.Errorf("Context name not found '%s' in %s", "@context", out) if !strings.Contains(string(out), string(ContextKw)) { t.Errorf("Context name not found %q in %s", ContextKw, out) } if !strings.Contains(string(out), url) { t.Errorf("Context url not found '%s' in %s", url, out) t.Errorf("Context url not found %q in %s", url, out) } err = Unmarshal(out, &b) if err != nil { t.Errorf("%s", err) } if a.Id != b.Id { t.Errorf("Id isn't equal '%s' expected '%s'", a.Id, b.Id) t.Errorf("Id isn't equal %q expected %q in %s", a.Id, b.Id, out) } if a.Name != b.Name { t.Errorf("Name isn't equal '%s' expected '%s'", a.Name, b.Name) t.Errorf("Name isn't equal %q expected %q", a.Name, b.Name) } if a.Type != b.Type { t.Errorf("Type isn't equal '%s' expected '%s'", a.Type, b.Type) t.Errorf("Type isn't equal %q expected %q", a.Type, b.Type) } if a.PropA != b.PropA { t.Errorf("PropA isn't equal '%s' expected '%s'", a.PropA, b.PropA) t.Errorf("PropA isn't equal %q expected %q", a.PropA, b.PropA) } if a.PropB != b.PropB { t.Errorf("PropB isn't equal %f expected %f", a.PropB, b.PropB)
-
@@ -74,7 +75,7 @@ func TestMarshalNullContext(t *testing.T) {t.Errorf("%s", errJ) } if !bytes.Equal(outL, outJ) { t.Errorf("Json output should be euqlal '%s', received '%s'", outL, outJ) t.Errorf("Json output should be euqlal %q, received %q", outL, outJ) } }
-
@@ -112,29 +113,28 @@ func TestIsEmpty(t *testing.T) {} } func TestPayloadWithContext_MarshalJSON(t *testing.T) { empty := payloadWithContext{} eData, eErr := empty.MarshalJSON() func TestWithContext_MarshalJSON(t *testing.T) { tv := "value_test" v := struct{ Test string }{Test: tv} if eErr != nil { t.Errorf("Error: %s", eErr) } Ctx = nil n, _ := Marshal(nil) if bytes.Compare(eData, n) != 0 { t.Errorf("Empty payload should resolve to null json value '%s', received '%s'", n, eData) data, err := WithContext(IRI("http://example.com")).Marshal(v) if err != nil { t.Error(err) } if !bytes.Contains(data, []byte(ContextKw)) { t.Errorf("%q not found in %s", ContextKw, data) } m := reflect.TypeOf(v) mv := reflect.ValueOf(v) for i := 0; i < m.NumField(); i++ { f := m.Field(i) v := mv.Field(i) if !bytes.Contains(data, []byte(f.Name)) { t.Errorf("%q not found in %s", f.Name, data) } if !bytes.Contains(data, []byte(v.String())) { t.Errorf("%q not found in %s", v.String(), data) } } var a interface{} a = 1 p := payloadWithContext{Obj: a} pData, pErr := p.MarshalJSON() if pErr != nil { t.Errorf("Error: %s", pErr) } av, _ := Marshal(a) if bytes.Compare(pData, av) != 0 { t.Errorf("Empty payload should resolve to value '%s', received '%s'", av, pData) } }
-
-
-
@@ -13,7 +13,9 @@ package jsonld// This file starts with two simple examples using the scanner // before diving into the scanner itself. import "strconv" import ( "strconv" ) // Valid reports whether data is a valid JSON encoding. func Valid(data []byte) bool {
-
@@ -114,7 +116,7 @@ type scanner struct {// assigned to scanner.state and the method scanner.eof. // They give details about the current state of the scan that // callers might be interested to know about. // It is okay to ignore the return value of any particular // It is okay to Ignore the return value of any particular // call to scanner.state: if one call returns scanError, // every subsequent call will return scanError too. const (
-
@@ -133,6 +135,8 @@ const (// Stop. scanEnd // top-level value ended *before* this byte; known to be first "stop" result scanError // hit an error, scanner.err. scanFindType // need to find the "jsonld:type" element of the object. ) // These values are stored in the parseState stack.
-