diff --git a/ld/api_compact.go b/ld/api_compact.go index 912dea1..c470057 100644 --- a/ld/api_compact.go +++ b/ld/api_compact.go @@ -1,4 +1,5 @@ // Copyright 2015-2017 Piprate Limited +// Copyright 2025 Siemens AG // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -48,9 +49,8 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element } // use any scoped context on activeProperty - td := activeCtx.GetTermDefinition(activeProperty) - if ctx, hasCtx := td["@context"]; hasCtx { - newCtx, err := activeCtx.parse(ctx, make([]string, 0), false, true, false, true) + if td := activeCtx.GetTermDefinition(activeProperty); td != nil && td.hasContext { + newCtx, err := activeCtx.parse(td.context, make([]string, 0), false, true, false, true) if err != nil { return nil, err } @@ -66,7 +66,10 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element return nil, err } - propType := activeCtx.GetTermDefinition(activeProperty)["@type"] + propType := "" + if td := activeCtx.GetTermDefinition(activeProperty); td != nil { + propType = td.typ + } if _, isMap := compactedValue.(map[string]interface{}); !isMap || propType == "@json" { return compactedValue, nil } @@ -94,9 +97,8 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element } // apply property-scoped context after reverting term-scoped context - propertyScopedCtx := inputCtx.GetTermDefinition(activeProperty)["@context"] - if propertyScopedCtx != nil { - newCtx, err := activeCtx.parse(propertyScopedCtx, nil, false, true, false, true) + if td := inputCtx.GetTermDefinition(activeProperty); td != nil && td.context != nil { + newCtx, err := activeCtx.parse(td.context, nil, false, true, false, true) if err != nil { return nil, err } @@ -122,9 +124,8 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element // process in lexicographical order, see https://github.com/json-ld/json-ld.org/issues/616 sort.Strings(types) for _, tt := range types { - td := inputCtx.GetTermDefinition(tt) - if ctx, hasCtx := td["@context"]; hasCtx { - newCtx, err := activeCtx.parse(ctx, nil, false, false, false, false) + if td := inputCtx.GetTermDefinition(tt); td != nil && td.hasContext { + newCtx, err := activeCtx.parse(td.context, nil, false, false, false, false) if err != nil { return nil, err } @@ -273,15 +274,15 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element } nestResult := result - nestProperty, hasNest := activeCtx.GetTermDefinition(itemActiveProperty)["@nest"] - if hasNest { - if err := api.checkNestProperty(activeCtx, nestProperty.(string)); err != nil { + if td := activeCtx.GetTermDefinition(itemActiveProperty); td != nil && td.nest != "" { + nestProperty := td.nest + if err := api.checkNestProperty(activeCtx, nestProperty); err != nil { return nil, err } - if _, isMap := result[nestProperty.(string)].(map[string]interface{}); !isMap { - result[nestProperty.(string)] = make(map[string]interface{}) + if _, isMap := result[nestProperty].(map[string]interface{}); !isMap { + result[nestProperty] = make(map[string]interface{}) } - nestResult = result[nestProperty.(string)].(map[string]interface{}) + nestResult = result[nestProperty].(map[string]interface{}) } AddValue(nestResult, itemActiveProperty, make([]interface{}, 0), true, false, true, false) @@ -302,15 +303,16 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element // if itemActiveProperty is a @nest property, add values to nestResult, otherwise result nestResult := result - nestProperty, hasNest := activeCtx.GetTermDefinition(itemActiveProperty)["@nest"] - if hasNest { - if err := api.checkNestProperty(activeCtx, nestProperty.(string)); err != nil { + + if td := activeCtx.GetTermDefinition(itemActiveProperty); td != nil && td.nest != "" { + nestProperty := td.nest + if err := api.checkNestProperty(activeCtx, nestProperty); err != nil { return nil, err } - if _, isMap := result[nestProperty.(string)].(map[string]interface{}); !isMap { - result[nestProperty.(string)] = make(map[string]interface{}) + if _, isMap := result[nestProperty].(map[string]interface{}); !isMap { + result[nestProperty] = make(map[string]interface{}) } - nestResult = result[nestProperty.(string)].(map[string]interface{}) + nestResult = result[nestProperty].(map[string]interface{}) } // get @list value if appropriate @@ -471,12 +473,13 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element mapKey = v.(string) } } else if isIndexContainer { - indexKey := activeCtx.GetTermDefinition(itemActiveProperty)["@index"] - if indexKey == nil { - indexKey = "@index" + + indexKey := "@index" + if td := activeCtx.GetTermDefinition(itemActiveProperty); td != nil && td.index != "" { + indexKey = td.index } - containerKey, err := activeCtx.CompactIri(indexKey.(string), nil, true, false) + containerKey, err := activeCtx.CompactIri(indexKey, nil, true, false) if err != nil { return nil, err } @@ -490,7 +493,7 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element var propsArray []interface{} compactedItemMap, isMap := compactedItem.(map[string]interface{}) if isMap { - props, found := compactedItemMap[indexKey.(string)] + props, found := compactedItemMap[indexKey] if found { propsArray = Arrayify(props) } else { @@ -510,11 +513,11 @@ func (api *JsonLdApi) Compact(activeCtx *Context, activeProperty string, element } else { switch len(others) { case 0: - delete(compactedItemMap, indexKey.(string)) + delete(compactedItemMap, indexKey) case 1: - compactedItemMap[indexKey.(string)] = others[0] + compactedItemMap[indexKey] = others[0] default: - compactedItemMap[indexKey.(string)] = others + compactedItemMap[indexKey] = others } } } diff --git a/ld/api_expand.go b/ld/api_expand.go index 45276a0..6d85b37 100644 --- a/ld/api_expand.go +++ b/ld/api_expand.go @@ -1,4 +1,5 @@ // Copyright 2015-2017 Piprate Limited +// Copyright 2025 Siemens AG // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -85,7 +86,10 @@ func (api *JsonLdApi) Expand(activeCtx *Context, activeProperty string, element } // Get any property-scoped context for activeProperty - propertyScopedCtx := activeCtx.GetTermDefinition(activeProperty)["@context"] + var propertyScopedCtx interface{} + if td := activeCtx.GetTermDefinition(activeProperty); td != nil { + propertyScopedCtx = td.context + } // second, determine if any type-scoped context should be reverted; it // should only be reverted when the following are all true: @@ -178,9 +182,8 @@ func (api *JsonLdApi) Expand(activeCtx *Context, activeProperty string, element } for _, tt := range types { - td := typeScopedContext.GetTermDefinition(tt) - if ctx, hasCtx := td["@context"]; hasCtx { - newCtx, err := activeCtx.parse(ctx, nil, false, false, false, false) + if td := typeScopedContext.GetTermDefinition(tt); td != nil && td.hasContext { + newCtx, err := activeCtx.parse(td.context, nil, false, false, false, false) if err != nil { return nil, err } @@ -666,10 +669,10 @@ func (api *JsonLdApi) expandObject(activeCtx *Context, activeProperty string, ex // use potential scoped context for key termCtx := activeCtx td := activeCtx.GetTermDefinition(key) - if ctx, hasCtx := td["@context"]; hasCtx { + if td != nil && td.hasContext { // TODO: fix calling a private method //termCtx, err = activeCtx.Parse(ctx) - termCtx, err = activeCtx.parse(ctx, make([]string, 0), false, true, false, true) + termCtx, err = activeCtx.parse(td.context, make([]string, 0), false, true, false, true) if err != nil { return err } @@ -679,7 +682,8 @@ func (api *JsonLdApi) expandObject(activeCtx *Context, activeProperty string, ex if termCtx.HasContainerMapping(key, "@language") && isMap { var expandedValueList []interface{} - dir, hasDir := td["@direction"] + dir, hasDir := td.direction, td.hasDirection + for _, language := range GetOrderedKeys(valueMap) { expandedLanguage, err := termCtx.ExpandIri(language, false, true, nil, nil) if err != nil { @@ -706,7 +710,7 @@ func (api *JsonLdApi) expandObject(activeCtx *Context, activeProperty string, ex if dir != nil { v["@direction"] = dir } - } else if defaultDir, found := termCtx.values["@direction"]; found { + } else if defaultDir := termCtx.values.direction; defaultDir != "" { v["@direction"] = defaultDir } expandedValueList = append(expandedValueList, v) @@ -715,18 +719,18 @@ func (api *JsonLdApi) expandObject(activeCtx *Context, activeProperty string, ex expandedValue = expandedValueList } else if termCtx.HasContainerMapping(key, "@index") && isMap { // 7.6) asGraph := termCtx.HasContainerMapping(key, "@graph") - indexKey := termCtx.GetTermDefinition(key)["@index"] - if indexKey == nil { - indexKey = "@index" + indexKey := "@index" + if tdKey := termCtx.GetTermDefinition(key); tdKey != nil && tdKey.index != "" { + indexKey = tdKey.index } var propertyIndex string if indexKey != "@index" { - propertyIndex, err = activeCtx.ExpandIri(indexKey.(string), false, true, nil, nil) + propertyIndex, err = activeCtx.ExpandIri(indexKey, false, true, nil, nil) if err != nil { return err } } - expandedValue, err = api.expandIndexMap(termCtx, key, valueMap, indexKey.(string), asGraph, propertyIndex, + expandedValue, err = api.expandIndexMap(termCtx, key, valueMap, indexKey, asGraph, propertyIndex, opts) if err != nil { return err @@ -747,6 +751,7 @@ func (api *JsonLdApi) expandObject(activeCtx *Context, activeProperty string, ex } } else { isList := expandedProperty == "@list" + tdKey := activeCtx.GetTermDefinition(key) if isList || expandedProperty == "@set" { nextActiveProperty := activeProperty if isList && expandedActiveProperty == "@graph" { @@ -756,7 +761,7 @@ func (api *JsonLdApi) expandObject(activeCtx *Context, activeProperty string, ex if err != nil { return err } - } else if activeCtx.GetTermDefinition(key)["@type"] == "@json" { + } else if tdKey != nil && tdKey.typ == "@json" { expandedValue = map[string]interface{}{ "@type": "@json", "@value": value, @@ -908,15 +913,12 @@ func (api *JsonLdApi) expandIndexMap(activeCtx *Context, activeProperty string, indexCtx := activeCtx // if indexKey is @type, there may be a context defined for it - if indexKey == "@type" { - td := activeCtx.GetTermDefinition(key) - if ctx, hasCtx := td["@context"]; hasCtx { - newCtx, err := activeCtx.Parse(ctx) - if err != nil { - return nil, err - } - indexCtx = newCtx + if td := activeCtx.GetTermDefinition(key); indexKey == "@type" && td != nil && td.hasContext { + newCtx, err := activeCtx.Parse(td.context) + if err != nil { + return nil, err } + indexCtx = newCtx } // 7.6.2.1) diff --git a/ld/context.go b/ld/context.go index 75ffc40..bbdfef4 100644 --- a/ld/context.go +++ b/ld/context.go @@ -18,6 +18,7 @@ package ld import ( "errors" "fmt" + "reflect" "regexp" "sort" "strings" @@ -42,15 +43,77 @@ var ( // Context represents a JSON-LD context and provides easy access to specific // keys and operations. type Context struct { - values map[string]interface{} + values *Values options *JsonLdOptions - termDefinitions map[string]interface{} + termDefinitions map[string]*TermDefinition inverse map[string]interface{} protected map[string]bool previousContext *Context fastCurieMap map[string]interface{} } +type Values struct { + vocab *string + base string + direction string + version *float64 + processingMode string + typ string + language string + hasLanguage bool +} + +type TermDefinition struct { + id string + reverse bool + container []string + typ string + nest string + context interface{} + hasContext bool // nil is not the same as having explicitly "null" in the JSON-LD context + prefix *bool + index string + protected bool + direction *string + hasDirection bool + language *string + hasLanguage bool + termHasColon bool +} + +func (td *TermDefinition) Copy() *TermDefinition { + var result TermDefinition + result.id = td.id + result.reverse = td.reverse + result.container = td.container + result.typ = td.typ + result.context = td.context + result.hasContext = td.hasContext + result.prefix = td.prefix + result.nest = td.nest + result.index = td.index + result.protected = td.protected + result.direction = td.direction + result.hasDirection = td.hasDirection + result.language = td.language + result.hasLanguage = td.hasLanguage + return &result +} + +func DeepCompareTermDef(lhs *TermDefinition, rhs *TermDefinition, listOrderMatters bool) bool { + return lhs.id == rhs.id && + lhs.reverse == rhs.reverse && + lhs.typ == rhs.typ && + lhs.hasContext == rhs.hasContext && + lhs.nest == rhs.nest && + lhs.index == rhs.index && + lhs.protected == rhs.protected && + reflect.DeepEqual(lhs.container, rhs.container) && + reflect.DeepEqual(lhs.direction, rhs.direction) && lhs.hasDirection == rhs.hasDirection && + reflect.DeepEqual(lhs.language, rhs.language) && lhs.hasLanguage == rhs.hasLanguage && + DeepCompare(lhs.context, rhs.context, listOrderMatters) +} + type FastCurieEntry struct { iri string terms []string @@ -63,20 +126,27 @@ func NewContext(values map[string]interface{}, options *JsonLdOptions) *Context } context := &Context{ - values: make(map[string]interface{}), + values: &Values{}, options: options, - termDefinitions: make(map[string]interface{}), + termDefinitions: make(map[string]*TermDefinition), protected: make(map[string]bool), fastCurieMap: make(map[string]any), } - context.values["@base"] = options.Base + context.values.base = options.Base - for k, v := range values { - context.values[k] = v + if values != nil { + if val, ok := values["@vocab"]; ok { + s := val.(string) + context.values.vocab = &s + } + if val, ok := values["@language"]; ok { + context.values.language = val.(string) + context.values.hasLanguage = true + } } - context.values["processingMode"] = options.ProcessingMode + context.values.processingMode = options.ProcessingMode return context } @@ -97,7 +167,11 @@ func (c *Context) AsMap() map[string]interface{} { // CopyContext creates a full copy of the given context. func CopyContext(ctx *Context) *Context { - context := NewContext(ctx.values, ctx.options) + context := NewContext(nil, ctx.options) + if ctx.values != nil { + values := *ctx.values + context.values = &values + } for k, v := range ctx.termDefinitions { context.termDefinitions[k] = v @@ -187,7 +261,7 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi result = ctx // 3.2) case string: - uri := Resolve(result.values["@base"].(string), ctx) + uri := Resolve(result.values.base, ctx) // 3.2.2 for _, remoteCtx := range remoteContexts { if remoteCtx == uri { @@ -235,24 +309,23 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi } } - pm, hasProcessingMode := c.values["processingMode"] + pm := c.values.processingMode if versionValue, versionPresent := contextMap["@version"]; versionPresent { if versionValue != 1.1 { return nil, NewJsonLdError(InvalidVersionValue, fmt.Sprintf("unsupported JSON-LD version: %s", versionValue)) } - if hasProcessingMode { - if pm.(string) == JsonLd_1_0 { - return nil, NewJsonLdError(ProcessingModeConflict, fmt.Sprintf("@version: %v not compatible with %s", versionValue, pm)) - } + if pm == JsonLd_1_0 { + return nil, NewJsonLdError(ProcessingModeConflict, fmt.Sprintf("@version: %v not compatible with %s", versionValue, pm)) } - result.values["processingMode"] = JsonLd_1_1 - result.values["@version"] = versionValue - } else if !hasProcessingMode { + result.values.processingMode = JsonLd_1_1 + ver := versionValue.(float64) + result.values.version = &ver + } else if pm == "" { // if not set explicitly, set processingMode to "json-ld-1.0" - result.values["processingMode"] = JsonLd_1_0 + result.values.processingMode = JsonLd_1_0 } else { - result.values["processingMode"] = pm + result.values.processingMode = pm } // handle @import @@ -264,7 +337,7 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi if !isString { return nil, NewJsonLdError(InvalidImportValue, "@import must be a string") } - uri := Resolve(result.values["@base"].(string), importStr) + uri := Resolve(result.values.base, importStr) rd, err := c.options.DocumentLoader.LoadDocument(uri) if err != nil { @@ -299,16 +372,16 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi baseValue, basePresent := contextMap["@base"] if !parsingARemoteContext && basePresent { if baseValue == nil { - delete(result.values, "@base") + result.values.base = "" } else if baseString, isString := baseValue.(string); isString { if IsAbsoluteIri(baseString) { - result.values["@base"] = baseValue + result.values.base = baseString } else { - baseURI := result.values["@base"].(string) + baseURI := result.values.base if !IsAbsoluteIri(baseURI) { return nil, NewJsonLdError(InvalidBaseIRI, baseURI) } - result.values["@base"] = Resolve(baseURI, baseString) + result.values.base = Resolve(baseURI, baseString) } } else { return nil, NewJsonLdError(InvalidBaseIRI, "the value of @base in a @context must be a string or null") @@ -318,9 +391,11 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi // handle @language if languageValue, languagePresent := contextMap["@language"]; languagePresent { if languageValue == nil { - delete(result.values, "@language") + result.values.hasLanguage = false + result.values.language = "" } else if languageString, isString := languageValue.(string); isString { - result.values["@language"] = strings.ToLower(languageString) + result.values.hasLanguage = true + result.values.language = strings.ToLower(languageString) } else { return nil, NewJsonLdError(InvalidDefaultLanguage, languageValue) } @@ -329,10 +404,10 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi // handle @direction if directionValue, directionPresent := contextMap["@direction"]; directionPresent { if directionValue == nil { - delete(result.values, "@direction") + result.values.direction = "" } else if directionString, isString := directionValue.(string); isString { if directionString == "rtl" || directionString == "ltr" { - result.values["@direction"] = strings.ToLower(directionString) + result.values.direction = strings.ToLower(directionString) } else { return nil, NewJsonLdError(InvalidBaseDirection, directionValue) } @@ -350,7 +425,7 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi if propagateValue, propagatePresent := contextMap["@propagate"]; propagatePresent { if c.processingMode(1.0) { return nil, NewJsonLdError(InvalidContextEntry, - fmt.Sprintf("@propagate not compatible with %s", c.values["processingMode"])) + fmt.Sprintf("@propagate not compatible with %s", c.values.processingMode)) } if _, isBool := propagateValue.(bool); isBool { defined["@propagate"] = true @@ -361,7 +436,7 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi if vocabValue, vocabPresent := contextMap["@vocab"]; vocabPresent { if vocabValue == nil { - delete(result.values, "@vocab") + result.values.vocab = nil } else if vocabString, isString := vocabValue.(string); isString { if !IsAbsoluteIri(vocabString) && c.processingMode(1.0) { return nil, NewJsonLdError(InvalidVocabMapping, "@vocab must be an absolute IRI in 1.0 mode") @@ -370,7 +445,7 @@ func (c *Context) parse(localContext interface{}, remoteContexts []string, parsi if err != nil { return nil, err } - result.values["@vocab"] = expandedVocab + result.values.vocab = &expandedVocab } else { return nil, NewJsonLdError(InvalidVocabMapping, "@vocab must be a string or null") } @@ -425,10 +500,21 @@ func (c *Context) CompactValue(activeProperty string, value map[string]interface } } - propType := c.GetTermDefinition(activeProperty)["@type"] + propType := "" + if td := c.GetTermDefinition(activeProperty); td != nil { + propType = td.typ + } + + languageVal := "" + if s := value["@language"]; s != nil { + languageVal = s.(string) + } + + directionVal := "" + if s := value["@direction"]; s != nil { + directionVal = s.(string) + } - languageVal := value["@language"] - directionVal := value["@direction"] var err error if hasID && idOrIndex { // 4 @@ -528,16 +614,16 @@ func (c *Context) CompactValue(activeProperty string, value map[string]interface // processingMode returns true if the given version is compatible with the current processing mode func (c *Context) processingMode(version float64) bool { - mode, hasMode := c.values["processingMode"] + mode := c.values.processingMode if version >= 1.1 { - if hasMode { - return mode.(string) >= fmt.Sprintf("json-ld-%v", version) + if mode != "" { + return mode >= fmt.Sprintf("json-ld-%v", version) } else { return false } } else { - if hasMode { - return mode.(string) == JsonLd_1_0 + if mode != "" { + return mode == JsonLd_1_0 } else { return true } @@ -608,7 +694,7 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri val := mapValue // 9) create a new term definition - var definition = make(map[string]interface{}) + var definition = &TermDefinition{} // make sure term definition only has expected keywords validKeys := map[string]bool{ @@ -636,8 +722,8 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri colIndex := strings.Index(term, ":") termHasColon := colIndex > 0 - definition["@reverse"] = false - definition["termHasColon"] = termHasColon + definition.reverse = false + definition.termHasColon = termHasColon // 11) if reverseValue, present := val["@reverse"]; present { @@ -666,8 +752,8 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri return nil } - definition["@id"] = id - definition["@reverse"] = true + definition.id = id + definition.reverse = true } else if idValue, hasID := val["@id"]; hasID { // 13) idStr, isString := idValue.(string) if !isString { @@ -689,7 +775,7 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri if res == "@context" { return NewJsonLdError(InvalidKeywordAlias, "cannot alias @context") } - definition["@id"] = res + definition.id = res if iriLikeTermPattern.MatchString(term) { defined[term] = true @@ -721,7 +807,8 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri } } - definition["_prefix"] = !termHasColon && termHasSuffix && (simpleTerm || c.processingMode(1.0)) + prefix := !termHasColon && termHasSuffix && (simpleTerm || c.processingMode(1.0)) + definition.prefix = &prefix } else { return NewJsonLdError(InvalidIRIMapping, "resulting IRI mapping should be a keyword, absolute IRI or blank node") @@ -730,7 +817,7 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri // 14) } - if _, hasID := definition["@id"]; !hasID { + if definition.id == "" { if termHasColon { prefix := term[0:colIndex] if _, containsPrefix := context[prefix]; containsPrefix { @@ -739,15 +826,14 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri } } if termDef, hasTermDef := c.termDefinitions[prefix]; hasTermDef { - termDefMap, _ := termDef.(map[string]interface{}) suffix := term[colIndex+1:] - definition["@id"] = termDefMap["@id"].(string) + suffix + definition.id = termDef.id + suffix } else { - definition["@id"] = term + definition.id = term } // 15) - } else if vocabValue, containsVocab := c.values["@vocab"]; containsVocab { - definition["@id"] = vocabValue.(string) + term + } else if vocabValue := c.values.vocab; vocabValue != nil { + definition.id = *vocabValue + term } else if term != "@type" { return NewJsonLdError(InvalidIRIMapping, "relative term definition without vocab mapping") } @@ -757,7 +843,7 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri valProtected, protectedFound := mapValue["@protected"] if (protectedFound && valProtected.(bool)) || (defined["@protected"] && !(protectedFound && !valProtected.(bool))) { c.protected[term] = true - definition["protected"] = true + definition.protected = true } defined[term] = true @@ -792,22 +878,22 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri } // add @type to mapping - definition["@type"] = typeStr + definition.typ = typeStr } // 16) if containerVal, hasContainer := val["@container"]; hasContainer { containerArray, isArray := containerVal.([]interface{}) - var container []interface{} + var container []string containerValueMap := make(map[string]bool) if isArray { - container = make([]interface{}, 0) + container = make([]string, 0) for _, c := range containerArray { - container = append(container, c) + container = append(container, c.(string)) containerValueMap[c.(string)] = true } } else { - container = []interface{}{containerVal} + container = []string{containerVal.(string)} containerValueMap[containerVal.(string)] = true } @@ -854,11 +940,11 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri if _, hasType := containerValueMap["@type"]; hasType { // if mapping does not have an @type, set it to @id - if _, tdHasType := definition["@type"]; !tdHasType { - definition["@type"] = "@id" + if definition.typ == "" { + definition.typ = "@id" } - if definition["@type"] != "@id" && definition["@type"] != "@vocab" { + if definition.typ != "@id" && definition.typ != "@vocab" { return NewJsonLdError(InvalidTypeMapping, "container: @type requires @type to be @id or @vocab") } @@ -872,7 +958,7 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri // check against valid containers for _, v := range container { - if _, isValidContainer := validContainers[v.(string)]; !isValidContainer { + if _, isValidContainer := validContainers[v]; !isValidContainer { allowedValues := make([]string, 0) for k := range validContainers { allowedValues = append(allowedValues, k) @@ -889,7 +975,7 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri return NewJsonLdError(InvalidContainerMapping, "@set not allowed with @list") } - if reverseVal, hasReverse := definition["@reverse"]; hasReverse && reverseVal.(bool) { + if definition.reverse { for key := range containerValueMap { if key != "@index" && key != "@set" { @@ -899,40 +985,45 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri } } - definition["@container"] = container + definition.container = container if term == "@type" { - definition["@id"] = "@type" + definition.id = "@type" } } // property indexing if indexVal, hasIndex := val["@index"]; hasIndex { _, hasContainer := val["@container"] - _, tdHasContainer := definition["@container"] + tdHasContainer := definition.container != nil if !hasContainer || !tdHasContainer { return NewJsonLdError(InvalidTermDefinition, fmt.Sprintf("@index without @index in @container: %s on term %s", indexVal, term)) } - if indexStr, isString := indexVal.(string); !isString || strings.HasPrefix(indexStr, "@") { + indexStr, isString := indexVal.(string) + if !isString || strings.HasPrefix(indexStr, "@") { return NewJsonLdError(InvalidTermDefinition, fmt.Sprintf("@index must expand to an IRI: %s on term %s", indexVal, term)) } - definition["@index"] = indexVal + definition.index = indexStr } // scoped contexts if ctxVal, hasCtx := val["@context"]; hasCtx { - definition["@context"] = ctxVal + definition.context = ctxVal + definition.hasContext = true } // 17) _, hasType := val["@type"] if languageVal, hasLanguage := val["@language"]; hasLanguage && !hasType { if language, isString := languageVal.(string); isString { - definition["@language"] = strings.ToLower(language) + s := strings.ToLower(language) + definition.language = &s + definition.hasLanguage = true } else if languageVal == nil { - definition["@language"] = nil + definition.language = nil + definition.hasLanguage = true } else { return NewJsonLdError(InvalidLanguageMapping, "@language must be a string or null") } @@ -947,18 +1038,21 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri if !isBool { return NewJsonLdError(InvalidPrefixValue, "@context value for @prefix must be boolean") } - if idVal, hasID := definition["@id"]; hasID && IsKeyword(idVal) { + if IsKeyword(definition.id) { return NewJsonLdError(InvalidTermDefinition, "keywords may not be used as prefixes") } - definition["_prefix"] = prefix + definition.prefix = &prefix } // handle direction if directionVal, hasDirection := val["@direction"]; hasDirection { if dir, isString := directionVal.(string); isString { - definition["@direction"] = strings.ToLower(dir) + s := strings.ToLower(dir) + definition.direction = &s + definition.hasDirection = true } else if directionVal == nil { - definition["@direction"] = nil + definition.direction = nil + definition.hasDirection = true } else { return NewJsonLdError(InvalidBaseDirection, fmt.Sprintf("direction must be null, 'ltr', or 'rtl', was %s on term %s", directionVal, term)) @@ -972,23 +1066,21 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri return NewJsonLdError(InvalidNestValue, "@context @nest value must be a string which is not a keyword other than @nest") } - definition["@nest"] = nest + definition.nest = nest } // disallow aliasing @context and @preserve - id := definition["@id"] - if id == "@context" || id == "@preserve" { + if id := definition.id; id == "@context" || id == "@preserve" { return NewJsonLdError(InvalidKeywordAlias, "@context and @preserve cannot be aliased") } // Check for overriding protected terms if prevDefinition != nil { - prevDefMap := prevDefinition.(map[string]interface{}) - if protectedVal, found := prevDefMap["protected"]; found && protectedVal.(bool) && !overrideProtected { + if protectedVal := prevDefinition.protected; protectedVal && !overrideProtected { // force new term to continue to be protected and see if the mappings would be equal c.protected[term] = true - definition["protected"] = true - if !DeepCompare(prevDefinition, definition, false) { + definition.protected = true + if !DeepCompareTermDef(prevDefinition, definition, false) { return NewJsonLdError(ProtectedTermRedefinition, "invalid JSON-LD syntax; tried to redefine a protected term") } } @@ -1040,9 +1132,8 @@ func (c *Context) ExpandIri(value string, relative bool, vocab bool, context map } // 3) if termDef, hasTermDef := c.termDefinitions[value]; vocab && hasTermDef { - termDefMap, isMap := termDef.(map[string]interface{}) - if isMap && termDefMap != nil { - return termDefMap["@id"].(string), nil + if termDef != nil { + return termDef.id, nil } return "", nil @@ -1071,9 +1162,8 @@ func (c *Context) ExpandIri(value string, relative bool, vocab bool, context map // If active context contains a term definition for prefix, return the result of concatenating // the IRI mapping associated with prefix and suffix. termDef, hasPrefix := c.termDefinitions[prefix] - if hasPrefix && termDef.(map[string]interface{})["@id"] != "" && termDef.(map[string]interface{})["_prefix"].(bool) { - termDefMap := termDef.(map[string]interface{}) - return termDefMap["@id"].(string) + suffix, nil + if hasPrefix && termDef.id != "" && termDef.prefix != nil && *termDef.prefix { + return termDef.id + suffix, nil } else if IsAbsoluteIri(value) { // Otherwise, if the value has the form of an absolute IRI, return it return value, nil @@ -1082,14 +1172,15 @@ func (c *Context) ExpandIri(value string, relative bool, vocab bool, context map } // 5) - if vocabValue, containsVocab := c.values["@vocab"]; vocab && containsVocab { - return vocabValue.(string) + value, nil + if vocabValue := c.values.vocab; vocab && vocabValue != nil { + return *vocabValue + value, nil } else if relative { // 6) - baseValue, hasBase := c.values["@base"] + baseValue := c.values.base + hasBase := baseValue != "" var base string if hasBase { - base = baseValue.(string) + base = baseValue } else { base = "" } @@ -1137,12 +1228,12 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool if relativeToVocab { if _, containsIRI := inverseCtx[iri]; containsIRI { var defaultLanguage string - langVal, hasLang := c.values["@language"] - if dir, dirFound := c.values["@direction"]; dirFound { + langVal, hasLang := c.values.language, c.values.hasLanguage + if dir := c.values.direction; hasLang && dir != "" { defaultLanguage = fmt.Sprintf("%s_%s", langVal, dir) } else { if hasLang { - defaultLanguage = langVal.(string) + defaultLanguage = langVal } else { defaultLanguage = "@none" } @@ -1346,7 +1437,7 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool resultVal, hasResult := c.termDefinitions[result] check := false if hasResult { - resultIDVal, hasResultID := resultVal.(map[string]interface{})["@id"] + resultIDVal, hasResultID := resultVal.id, resultVal.id != "" check = hasResultID && idVal == resultIDVal } if check { @@ -1382,9 +1473,9 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool } // 3) - if vocabVal, containsVocab := c.values["@vocab"]; containsVocab { + if vocabVal := c.values.vocab; vocabVal != nil { // determine if vocab is a prefix of the iri - vocab := vocabVal.(string) + vocab := *vocabVal // 3.1) if strings.HasPrefix(iri, vocab) && iri != vocab { // use suffix as relative iri if it is not a term in the @@ -1425,17 +1516,16 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool if !ok { continue } - termDef := termDefinitionAny.(map[string]interface{}) // a CURIE is usable if: // 1. it has no mapping, OR // 2. value is null, which means we're not compacting an @value, AND // the mapping matches the IRI curie := term + ":" + iri[len(entry.iri):] - prefix, hasPrefix := termDef["_prefix"] + prefix := termDefinitionAny.prefix curieMapping, hasCurie := c.termDefinitions[curie] - isUsableCurie := hasPrefix && prefix.(bool) && (!hasCurie || value == nil && curieMapping.(map[string]any)["@id"].(string) == iri) + isUsableCurie := prefix != nil && *prefix && (!hasCurie || value == nil && curieMapping.id == iri) // select curie if it is shorter or the same length but lexicographically // less than the current choice @@ -1452,9 +1542,9 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool // If iri could be confused with a compact IRI using a term in this context, // signal an error for term, td := range c.termDefinitions { - if tdMap, isMap := td.(map[string]interface{}); isMap { - prefix, hasPrefix := tdMap["_prefix"] - if hasPrefix && prefix.(bool) && strings.HasPrefix(iri, term+":") { + if td != nil { + prefix := td.prefix + if prefix != nil && *prefix && strings.HasPrefix(iri, term+":") { return "", NewJsonLdError(IRIConfusedWithPrefix, fmt.Sprintf("Absolute IRI %s confused with prefix %s", iri, term)) } } @@ -1462,7 +1552,7 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool // compact IRI relative to base if !relativeToVocab { - return RemoveBase(c.values["@base"], iri), nil + return RemoveBase(c.values.base, iri), nil } // return IRI as is @@ -1481,17 +1571,10 @@ func (c *Context) GetPrefixes(onlyCommonPrefixes bool) map[string]string { prefixes := make(map[string]string) for term, termDefinition := range c.termDefinitions { - if strings.Contains(term, ":") { - continue - } - if termDefinition == nil { - continue - } - termDefinitionMap := termDefinition.(map[string]interface{}) - id := termDefinitionMap["@id"].(string) - if id == "" { + if strings.Contains(term, ":") || termDefinition == nil || termDefinition.id == "" { continue } + id := termDefinition.id if strings.HasPrefix(term, "@") || strings.HasPrefix(id, "@") { continue } @@ -1518,9 +1601,8 @@ func (c *Context) GetInverse() map[string]interface{} { // 2) defaultLanguage := "@none" - langVal, hasLang := c.values["@language"] - if hasLang { - defaultLanguage = langVal.(string) + if c.values.hasLanguage { + defaultLanguage = c.values.language } // create term selections for each mapping in the context, ordered by @@ -1532,36 +1614,32 @@ func (c *Context) GetInverse() map[string]interface{} { irisToTerms := make(map[string][]string, 0) for _, term := range terms { - definitionVal := c.termDefinitions[term] + definition := c.termDefinitions[term] // 3.1) - if definitionVal == nil { + if definition == nil { continue } - definition := definitionVal.(map[string]interface{}) // 3.2) var containerJoin string // this implementation was adapted from pyLD - containerVal, present := definition["@container"] - if !present { + if definition.container == nil { containerJoin = "@none" // see Ruby, as_set? } else { - container := containerVal.([]interface{}) - strList := make([]string, 0, len(container)) - for _, c := range container { - strList = append(strList, c.(string)) - } + container := definition.container + strList := make([]string, len(container)) + copy(strList, container) sort.Strings(strList) containerJoin = strings.Join(strList, "") } // 3.3) - iri := definition["@id"].(string) + iri := definition.id // 3.4 + 3.5) var containerMap map[string]interface{} containerMapVal, present := c.inverse[iri] isKeyword := IsKeyword(iri) - termHasColon := definition["termHasColon"].(bool) + termHasColon := definition.termHasColon if !present { containerMap = make(map[string]interface{}) c.inverse[iri] = containerMap @@ -1602,17 +1680,17 @@ func (c *Context) GetInverse() map[string]interface{} { typeLanguageMap = typeLanguageMapVal.(map[string]interface{}) } - langVal, hasLang := definition["@language"] - dirVal, hasDir := definition["@direction"] - typeVal, hasType := definition["@type"] + langVal, hasLang := definition.language, definition.hasLanguage + dirVal, hasDir := definition.direction, definition.hasDirection + typeVal := definition.typ // 3.8) - if reverseVal, hasValue := definition["@reverse"]; hasValue && reverseVal.(bool) { + if definition.reverse { typeMap := typeLanguageMap["@type"].(map[string]interface{}) if _, hasValue := typeMap["@reverse"]; !hasValue { typeMap["@reverse"] = term } - } else if hasType && typeVal == "@none" { + } else if typeVal == "@none" { typeMap := typeLanguageMap["@type"].(map[string]interface{}) if _, hasAny := typeMap["@any"]; !hasAny { typeMap["@any"] = term @@ -1625,21 +1703,21 @@ func (c *Context) GetInverse() map[string]interface{} { if _, hasAny := anyMap["@any"]; !hasAny { anyMap["@any"] = term } - } else if hasType { + } else if typeVal != "" { typeMap := typeLanguageMap["@type"].(map[string]interface{}) if _, hasValue := typeMap["@type"]; !hasValue { - typeMap[typeVal.(string)] = term + typeMap[typeVal] = term } } else if hasLang && hasDir { languageMap := typeLanguageMap["@language"].(map[string]interface{}) langDir := "@null" if langVal != nil && dirVal != nil { - langDir = fmt.Sprintf("%s_%s", langVal.(string), dirVal.(string)) + langDir = fmt.Sprintf("%s_%s", *langVal, *dirVal) } else if langVal != nil { - langDir = langVal.(string) + langDir = *langVal } else if dirVal != nil { - langDir = "_" + dirVal.(string) + langDir = "_" + *dirVal } if _, hasLang := languageMap[langDir]; !hasLang { languageMap[langDir] = term @@ -1648,7 +1726,7 @@ func (c *Context) GetInverse() map[string]interface{} { languageMap := typeLanguageMap["@language"].(map[string]interface{}) language := "@null" if langVal != nil { - language = langVal.(string) + language = *langVal } if _, hasLang := languageMap[language]; !hasLang { languageMap[language] = term @@ -1657,20 +1735,20 @@ func (c *Context) GetInverse() map[string]interface{} { languageMap := typeLanguageMap["@language"].(map[string]interface{}) dir := "@none" if dirVal != nil { - dir = "_" + dirVal.(string) + dir = "_" + *dirVal } if _, hasLang := languageMap[dir]; !hasLang { languageMap[dir] = term } - } else if defDir, found := c.values["@direction"]; found { + } else if defDir := c.values.direction; defDir != "" { languageMap := typeLanguageMap["@language"].(map[string]interface{}) typeMap := typeLanguageMap["@type"].(map[string]interface{}) var langDir string if hasLang { // does this ever happen? There is a check above for hasLang - langDir = fmt.Sprintf("%s_%s", langVal.(string), defDir.(string)) + langDir = fmt.Sprintf("%s_%s", *langVal, defDir) } else { - langDir = "_" + defDir.(string) + langDir = "_" + defDir } if _, hasLang := languageMap[langDir]; !hasLang { languageMap[langDir] = term @@ -1783,26 +1861,22 @@ func (c *Context) SelectTerm(iri string, containers []string, typeLanguage strin } // GetContainer retrieves container mapping for the given property. -func (c *Context) GetContainer(property string) []interface{} { - propertyMap, isMap := c.termDefinitions[property].(map[string]interface{}) - if isMap { - if container, hasContainer := propertyMap["@container"]; hasContainer { - return container.([]interface{}) - } +func (c *Context) GetContainer(property string) []string { + td, ok := c.termDefinitions[property] + if ok && td != nil && td.container != nil { + return td.container } - return []interface{}{} + return []string{} } // GetContainer retrieves container mapping for the given property. func (c *Context) HasContainerMapping(property string, val string) bool { - propertyMap, isMap := c.termDefinitions[property].(map[string]interface{}) - if isMap { - if container, hasContainer := propertyMap["@container"]; hasContainer { - for _, container := range container.([]interface{}) { - if container == val { - return true - } + td, ok := c.termDefinitions[property] + if ok && td != nil && td.container != nil { + for _, container := range td.container { + if container == val { + return true } } } @@ -1816,63 +1890,62 @@ func (c *Context) IsReverseProperty(property string) bool { if td == nil { return false } - reverse, containsReverse := td["@reverse"] - return containsReverse && reverse.(bool) + return td.reverse } // GetTypeMapping returns type mapping for the given property func (c *Context) GetTypeMapping(property string) string { - rval := "" - if defaultLang, hasDefault := c.values["@type"]; hasDefault { - rval = defaultLang.(string) - } + // default type + rval := c.values.typ td := c.GetTermDefinition(property) - if td != nil { - if val, contains := td["@type"]; contains && val != nil { - return val.(string) - } + if td != nil && td.typ != "" { + return td.typ } return rval } // GetLanguageMapping returns language mapping for the given property -func (c *Context) GetLanguageMapping(property string) interface{} { +func (c *Context) GetLanguageMapping(property string) string { td := c.GetTermDefinition(property) - if td != nil { - if val, found := td["@language"]; found { - return val + if td != nil && td.hasLanguage { + if td.language != nil { + return *td.language } + return "" } - if defaultLang, hasDefault := c.values["@language"]; hasDefault { - return defaultLang + if c.values.hasLanguage { + return c.values.language } - return nil + return "" } // GetDirectionMapping returns direction mapping for the given property -func (c *Context) GetDirectionMapping(property string) interface{} { +func (c *Context) GetDirectionMapping(property string) string { td := c.GetTermDefinition(property) - if td != nil { - if val, found := td["@direction"]; found { - return val + if td != nil && td.hasDirection { + if td.direction != nil { + return *td.direction } + return "" } - if defaultDir, hasDefault := c.values["@direction"]; hasDefault { + if defaultDir := c.values.direction; defaultDir != "" { return defaultDir } - return nil + return "" } // GetTermDefinition returns a term definition for the given key -func (c *Context) GetTermDefinition(key string) map[string]interface{} { - value, _ := c.termDefinitions[key].(map[string]interface{}) - return value +func (c *Context) GetTermDefinition(key string) *TermDefinition { + if value, ok := c.termDefinitions[key]; ok { + return value + } + return nil } // ExpandValue expands the given value by using the coercion and keyword rules in the context. @@ -1883,7 +1956,7 @@ func (c *Context) ExpandValue(activeProperty string, value interface{}) (interfa // If the active property has a type mapping in active context that is @id, return a new JSON object // containing a single key-value pair where the key is @id and the value is the result of using // the IRI Expansion algorithm, passing active context, value, and true for document relative. - if td != nil && td["@type"] == "@id" { + if td != nil && td.typ == "@id" { if strVal, isString := value.(string); isString { var err error rval["@id"], err = c.ExpandIri(strVal, true, false, nil, nil) @@ -1898,7 +1971,7 @@ func (c *Context) ExpandValue(activeProperty string, value interface{}) (interfa // If active property has a type mapping in active context that is @vocab, return a new JSON object // containing a single key-value pair where the key is @id and the value is the result of using // the IRI Expansion algorithm, passing active context, value, true for vocab, and true for document relative. - if td != nil && td["@type"] == "@vocab" { + if td != nil && td.typ == "@vocab" { if strVal, isString := value.(string); isString { var err error rval["@id"], err = c.ExpandIri(strVal, true, true, nil, nil) @@ -1914,25 +1987,26 @@ func (c *Context) ExpandValue(activeProperty string, value interface{}) (interfa // 3) rval["@value"] = value // 4) - if typeVal, containsType := td["@type"]; td != nil && containsType && typeVal != "@id" && typeVal != "@vocab" && - typeVal != "@none" { - rval["@type"] = typeVal + if td != nil && td.typ != "" && td.typ != "@id" && td.typ != "@vocab" && + td.typ != "@none" { + rval["@type"] = td.typ } else if _, isString := value.(string); isString { // 5.1) - langVal, containsLang := td["@language"] - if containsLang { + if containsLang := td != nil && td.hasLanguage; containsLang { + langVal := td.language if langVal != nil { - rval["@language"] = langVal.(string) + rval["@language"] = *langVal } - } else if defaultLangVal, hasDefaultLang := c.values["@language"]; hasDefaultLang { + } else if defaultLangVal := c.values.language; c.values.hasLanguage { rval["@language"] = defaultLangVal } - dirVal, containsDir := td["@direction"] - if containsDir { + + if containsDir := td != nil && td.hasDirection; containsDir { + dirVal := td.direction if dirVal != nil { - rval["@direction"] = dirVal.(string) + rval["@direction"] = *dirVal } - } else if dirVal := c.values["@direction"]; dirVal != nil { + } else if dirVal := c.values.direction; dirVal != "" { rval["@direction"] = dirVal } } @@ -1943,41 +2017,40 @@ func (c *Context) ExpandValue(activeProperty string, value interface{}) (interfa func (c *Context) Serialize() (map[string]interface{}, error) { ctx := make(map[string]interface{}) - baseVal, hasBase := c.values["@base"] + baseVal := c.values.base + hasBase := baseVal != "" if hasBase && baseVal != c.options.Base { ctx["@base"] = baseVal } - if versionVal, hasVersion := c.values["@version"]; hasVersion { - ctx["@version"] = versionVal + if versionVal := c.values.version; versionVal != nil { + ctx["@version"] = *versionVal } - if langVal, hasLang := c.values["@language"]; hasLang { + if langVal := c.values.language; c.values.hasLanguage { ctx["@language"] = langVal } - if dirVal, hasDir := c.values["@direction"]; hasDir { + if dirVal := c.values.direction; dirVal != "" { ctx["@direction"] = dirVal } - if vocabVal, hasVocab := c.values["@vocab"]; hasVocab { - ctx["@vocab"] = vocabVal + if vocabVal := c.values.vocab; vocabVal != nil { + ctx["@vocab"] = *vocabVal } - for term, definitionVal := range c.termDefinitions { + for term, definition := range c.termDefinitions { // Note: definitionVal may be nil for terms which are set to be ignored // (see the definition for null value in JSON-LD spec) - definition, _ := definitionVal.(map[string]interface{}) - langVal, hasLang := definition["@language"] - containerVal, hasContainer := definition["@container"] - typeMappingVal, hasType := definition["@type"] - reverseVal, hasReverse := definition["@reverse"] - if !hasLang && !hasContainer && !hasType && (!hasReverse || reverseVal == false) { + langVal, hasLang := definition.language, definition.hasLanguage + containerVal, hasContainer := definition.container, definition.container != nil + typeMapping := definition.typ + if !hasLang && !hasContainer && typeMapping == "" && !definition.reverse { var cid interface{} - id, hasID := definition["@id"] - if !hasID { + id := definition.id + if id == "" { cid = nil ctx[term] = cid } else if IsKeyword(id) { ctx[term] = id } else { var err error - cid, err = c.CompactIri(id.(string), nil, false, false) + cid, err = c.CompactIri(id, nil, false, false) if err != nil { return nil, err } @@ -1990,11 +2063,12 @@ func (c *Context) Serialize() (map[string]interface{}, error) { } } else { defn := make(map[string]interface{}) - cid, err := c.CompactIri(definition["@id"].(string), nil, false, false) + id := definition.id + cid, err := c.CompactIri(id, nil, false, false) if err != nil { return nil, err } - reverseProperty := reverseVal.(bool) + reverseProperty := definition.reverse if !(term == cid && !reverseProperty) { if reverseProperty { defn["@reverse"] = cid @@ -2002,8 +2076,7 @@ func (c *Context) Serialize() (map[string]interface{}, error) { defn["@id"] = cid } } - if hasType { - typeMapping := typeMappingVal.(string) + if typeMapping != "" { if IsKeyword(typeMapping) { defn["@type"] = typeMapping } else { @@ -2014,17 +2087,17 @@ func (c *Context) Serialize() (map[string]interface{}, error) { } } if hasContainer { - if av, isArray := containerVal.([]string); isArray && len(av) == 1 { - defn["@container"] = av[0] + if len(containerVal) == 1 { + defn["@container"] = containerVal[0] } else { defn["@container"] = containerVal } } if hasLang { - if langVal == false { + if langVal == nil { defn["@language"] = nil } else { - defn["@language"] = langVal + defn["@language"] = *langVal } } ctx[term] = defn diff --git a/ld/utils.go b/ld/utils.go index 66c010f..fde6384 100644 --- a/ld/utils.go +++ b/ld/utils.go @@ -551,17 +551,7 @@ func CloneDocument(value interface{}) interface{} { } // GetKeys returns all keys in the given object -func GetKeys(m map[string]interface{}) []string { - keys := make([]string, 0, len(m)) - for key := range m { - keys = append(keys, key) - } - - return keys -} - -// GetKeysString returns all keys in the given map[string]string -func GetKeysString(m map[string]string) []string { +func GetKeys[T any](m map[string]T) []string { keys := make([]string, 0, len(m)) for key := range m { keys = append(keys, key)