github.com/kaptinlin/jsonschema@v0.4.6/properties.go (about) 1 package jsonschema 2 3 import ( 4 "fmt" 5 "slices" 6 "strings" 7 ) 8 9 // EvaluateProperties checks if the properties in the data object conform to the schemas specified in the schema's properties attribute. 10 // According to the JSON Schema Draft 2020-12: 11 // - The value of "properties" must be an object, with each value being a valid JSON Schema. 12 // - Validation succeeds if, for each name that appears in both the instance and as a name within this keyword's value, the child instance for that name successfully validates against the corresponding schema. 13 // - This function also affects the validation of "additionalProperties" and "unevaluatedProperties" by determining which properties have been evaluated. 14 // 15 // This method ensures that each property in the data matches its defined schema. 16 // If a property does not conform, it returns a EvaluationError detailing the issue with that property. 17 // 18 // Reference: https://json-schema.org/draft/2020-12/json-schema-core#name-properties 19 func evaluateProperties(schema *Schema, object map[string]interface{}, evaluatedProps map[string]bool, _ map[int]bool, dynamicScope *DynamicScope) ([]*EvaluationResult, *EvaluationError) { 20 if schema.Properties == nil { 21 return nil, nil // No properties defined, nothing to do. 22 } 23 24 invalid_properties := []string{} 25 results := []*EvaluationResult{} 26 27 for propName, propSchema := range *schema.Properties { 28 evaluatedProps[propName] = true 29 propValue, exists := object[propName] 30 31 if exists { 32 result, _, _ := propSchema.evaluate(propValue, dynamicScope) 33 if result != nil { 34 //nolint:errcheck 35 result.SetEvaluationPath(fmt.Sprintf("/properties/%s", propName)). 36 SetSchemaLocation(schema.GetSchemaLocation(fmt.Sprintf("/properties/%s", propName))). 37 SetInstanceLocation(fmt.Sprintf("/%s", propName)) 38 39 results = append(results, result) 40 41 if !result.IsValid() { 42 invalid_properties = append(invalid_properties, propName) 43 } 44 } 45 } else if isRequired(schema, propName) && !defaultIsSpecified(propSchema) { 46 // Handle properties that are expected but not provided 47 result, _, _ := propSchema.evaluate(nil, dynamicScope) 48 49 if result != nil { 50 //nolint:errcheck 51 result.SetEvaluationPath(fmt.Sprintf("/properties/%s", propName)). 52 SetSchemaLocation(schema.GetSchemaLocation(fmt.Sprintf("/properties/%s", propName))). 53 SetInstanceLocation(fmt.Sprintf("/%s", propName)) 54 55 results = append(results, result) 56 57 if !result.IsValid() { 58 invalid_properties = append(invalid_properties, propName) 59 } 60 } 61 } 62 } 63 64 if len(invalid_properties) == 1 { 65 return results, NewEvaluationError("properties", "property_mismatch", "Property {property} does not match the schema", map[string]interface{}{ 66 "property": fmt.Sprintf("'%s'", invalid_properties[0]), 67 }) 68 } else if len(invalid_properties) > 1 { 69 slices.Sort(invalid_properties) 70 quotedProperties := make([]string, len(invalid_properties)) 71 for i, prop := range invalid_properties { 72 quotedProperties[i] = fmt.Sprintf("'%s'", prop) 73 } 74 return results, NewEvaluationError("properties", "properties_mismatch", "Properties {properties} do not match their schemas", map[string]interface{}{ 75 "properties": strings.Join(quotedProperties, ", "), 76 }) 77 } 78 79 return results, nil 80 } 81 82 // isRequired checks if a property is required. 83 func isRequired(schema *Schema, propName string) bool { 84 for _, reqProp := range schema.Required { 85 if reqProp == propName { 86 return true 87 } 88 } 89 return false 90 } 91 92 // defaultIsSpecified checks if a default value is specified for a property schema. 93 func defaultIsSpecified(propSchema *Schema) bool { 94 return propSchema != nil && propSchema.Default != nil 95 }