diff --git a/docs/spec/v2/metricset.json b/docs/spec/v2/metricset.json index 9bfc90d1564..bbd73837086 100644 --- a/docs/spec/v2/metricset.json +++ b/docs/spec/v2/metricset.json @@ -8,6 +8,10 @@ "additionalProperties": false, "patternProperties": { "^[^*\"]*$": { + "type": [ + "null", + "object" + ], "properties": { "value": { "description": "Value holds the value of a single metric sample.", diff --git a/model/modeldecoder/generator/jsonschema.go b/model/modeldecoder/generator/jsonschema.go index 16f7ae7714f..65e25ea8aa2 100644 --- a/model/modeldecoder/generator/jsonschema.go +++ b/model/modeldecoder/generator/jsonschema.go @@ -121,10 +121,6 @@ func (g *JSONSchemaGenerator) generate(st structType, key string, prop *property default: switch t := f.Type().Underlying().(type) { case *types.Map: - if _, ok := tags[tagPatternKeys]; !ok && name == "" { - // ignore the field in case no json name and no patternProperties are given - continue - } nestedProp := property{Properties: make(map[string]*property)} if err = generateJSONPropertyMap(&info, prop, &childProp, &nestedProp); err != nil { break @@ -240,7 +236,7 @@ type property struct { Type *propertyType `json:"type,omitempty"` // AdditionalProperties should default to `true` and be set to `false` // in case PatternProperties are set - AdditionalProperties *bool `json:"additionalProperties,omitempty"` + AdditionalProperties interface{} `json:"additionalProperties,omitempty"` PatternProperties map[string]*property `json:"patternProperties,omitempty"` Properties map[string]*property `json:"properties,omitempty"` Items *property `json:"items,omitempty"` diff --git a/model/modeldecoder/generator/map.go b/model/modeldecoder/generator/map.go index ab417ed8709..40bc9616ef3 100644 --- a/model/modeldecoder/generator/map.go +++ b/model/modeldecoder/generator/map.go @@ -159,19 +159,23 @@ func generateJSONPropertyMap(info *fieldInfo, parent *property, child *property, child.Type.add(TypeNameObject) patternName, isPatternProp := info.tags[tagPatternKeys] delete(info.tags, tagPatternKeys) - if !isPatternProp && name == "" { - // the object (map) can be places as a property with a defined key, - // or as a patternProperty with a defined key pattern - // if both are missing the field cannot be added - return fmt.Errorf("invalid combination: either json name or tag %s must be given", tagPatternKeys) - } - if !isPatternProp { - // if no pattern property is given, the child map will be nested directly as - // property inside the parent property, identified by it's json name - // e.g. {"parent":{"properties":{"jsonNameXY":{..}}}} + + nestedParent := child + if name == "" { + // The map does not have a json name defined, in which case it is nested directly + // inside the parent object's patternProperties/additionalProperties + // e.g. {"parent":{"patternProperties":{"patternXY":{..}}}} *nested = *child - parent.Properties[name] = nested + nestedParent = parent + } else { + // The map does have a json name defined, in which case it is nested as + // patternProperties/additionalProperties inside an object, which itself is nested + // inside the parent property, identified by its json name + // e.g. {"parent":{"properties":{"jsonNameXY":{"patternProperties":{"patternXY":{..}}}}}} + parent.Properties[name] = child } + + haveValueSchema := len(info.tags) > 0 if maxLen, ok := info.tags[tagMaxLengthVals]; ok { nested.MaxLength = json.Number(maxLen) delete(info.tags, tagMaxLengthVals) @@ -183,42 +187,30 @@ func generateJSONPropertyMap(info *fieldInfo, parent *property, child *property, } delete(info.tags, tagInputTypesVals) nested.Type = &propertyType{names: names} - } - if !isPatternProp { - // nothing more to do when no key pattern is given - return nil - } - pattern, ok := info.parsed.patternVariables[patternName] - if !ok { - return fmt.Errorf("unhandled %s tag value %s", tagPatternKeys, pattern) - } - // for map key patterns two options are supported: - // - the map does not have a json name defined, in which case it is nested directly - // inside the parent object's patternProperty - // e.g. {"parent":{"patternProperties":{"patternXY":{..}}}} - // - the map does have a json name defined, in which case it is nested as - // patternProperty inside an object, which itself is nested - // inside the parent property, identified by it's json name - // e.g. {"parent":{"properties":{"jsonNameXY":{"patternProperties":{"patternXY":{..}}}}}} - if name == "" { - if parent.PatternProperties == nil { - parent.PatternProperties = make(map[string]*property) - } + } else { valueType := info.field.Type().Underlying().(*types.Map).Elem() - typeName, ok := propertyTypes[valueType.String()] - if !ok { - typeName = TypeNameObject + if !types.IsInterface(valueType) { + haveValueSchema = true + typeName, ok := propertyTypes[valueType.String()] + if !ok { + typeName = TypeNameObject + } + nested.Type = &propertyType{names: []propertyTypeName{typeName}} } - nested.Type = &propertyType{names: []propertyTypeName{typeName}} - parent.PatternProperties[pattern] = nested - parent.AdditionalProperties = new(bool) - return nil } - if child.PatternProperties == nil { - child.PatternProperties = make(map[string]*property) + + if isPatternProp { + pattern, ok := info.parsed.patternVariables[patternName] + if !ok { + return fmt.Errorf("unhandled %s tag value %s", tagPatternKeys, pattern) + } + if nestedParent.PatternProperties == nil { + nestedParent.PatternProperties = make(map[string]*property) + } + nestedParent.PatternProperties[pattern] = nested + nestedParent.AdditionalProperties = false + } else if haveValueSchema { + nestedParent.AdditionalProperties = nested } - child.PatternProperties[pattern] = nested - child.AdditionalProperties = new(bool) - parent.Properties[name] = child return nil }