package jsonschema import ( "encoding/json" "fmt" "os" "slices" ) // Load a JSON document and validate it against the JSON schema. Instance here // refers to a JSON document. see: https://json-schema.org/draft/2020-12/json-schema-core.html#name-instance func (s *Schema) LoadInstance(path string) (map[string]any, error) { instance := make(map[string]any) b, err := os.ReadFile(path) if err != nil { return nil, err } err = json.Unmarshal(b, &instance) if err != nil { return nil, err } // The default JSON unmarshaler parses untyped number values as float64. // We convert integer properties from float64 to int64 here. for name, v := range instance { propertySchema, ok := s.Properties[name] if !ok { continue } if propertySchema.Type != IntegerType { continue } integerValue, err := toInteger(v) if err != nil { return nil, fmt.Errorf("failed to parse property %s: %w", name, err) } instance[name] = integerValue } return instance, s.ValidateInstance(instance) } func (s *Schema) ValidateInstance(instance map[string]any) error { for _, fn := range []func(map[string]any) error{ s.validateAdditionalProperties, s.validateEnum, s.validateRequired, s.validateTypes, } { err := fn(instance) if err != nil { return err } } return nil } // If additional properties is set to false, this function validates instance only // contains properties defined in the schema. func (s *Schema) validateAdditionalProperties(instance map[string]any) error { // Note: AdditionalProperties has the type any. if s.AdditionalProperties != false { return nil } for k := range instance { _, ok := s.Properties[k] if !ok { return fmt.Errorf("property %s is not defined in the schema", k) } } return nil } // This function validates that all require properties in the schema have values // in the instance. func (s *Schema) validateRequired(instance map[string]any) error { for _, name := range s.Required { if _, ok := instance[name]; !ok { return fmt.Errorf("no value provided for required property %s", name) } } return nil } // Validates the types of all input properties values match their types defined in the schema func (s *Schema) validateTypes(instance map[string]any) error { for k, v := range instance { fieldInfo, ok := s.Properties[k] if !ok { continue } err := validateType(v, fieldInfo.Type) if err != nil { return fmt.Errorf("incorrect type for property %s: %w", k, err) } } return nil } func (s *Schema) validateEnum(instance map[string]any) error { for k, v := range instance { fieldInfo, ok := s.Properties[k] if !ok { continue } if fieldInfo.Enum == nil { continue } if !slices.Contains(fieldInfo.Enum, v) { return fmt.Errorf("expected value of property %s to be one of %v. Found: %v", k, fieldInfo.Enum, v) } } return nil }