github.com/kaptinlin/jsonschema@v0.4.6/schema.go (about)

     1  package jsonschema
     2  
     3  import (
     4  	"regexp"
     5  
     6  	"github.com/goccy/go-json"
     7  )
     8  
     9  // Schema represents a JSON Schema as per the 2020-12 draft, containing all
    10  // necessary metadata and validation properties defined by the specification.
    11  type Schema struct {
    12  	compiledPatterns      map[string]*regexp.Regexp // Cached compiled regular expressions for pattern properties.
    13  	compiler              *Compiler                 // Reference to the associated Compiler instance.
    14  	parent                *Schema                   // Parent schema for hierarchical resolution.
    15  	uri                   string                    // Internal schema identifier resolved during compilation.
    16  	baseURI               string                    // Base URI for resolving relative references within the schema.
    17  	anchors               map[string]*Schema        // Anchors for quick lookup of internal schema references.
    18  	dynamicAnchors        map[string]*Schema        // Dynamic anchors for more flexible schema references.
    19  	schemas               map[string]*Schema        // Cache of compiled schemas.
    20  	compiledStringPattern *regexp.Regexp            // Cached compiled regular expressions for string patterns.
    21  
    22  	ID     string  `json:"$id,omitempty"`     // Public identifier for the schema.
    23  	Schema string  `json:"$schema,omitempty"` // URI indicating the specification the schema conforms to.
    24  	Format *string `json:"format,omitempty"`  // Format hint for string data, e.g., "email" or "date-time".
    25  
    26  	// Schema reference keywords, see https://json-schema.org/draft/2020-12/json-schema-core#ref
    27  	Ref                string             `json:"$ref,omitempty"`           // Reference to another schema.
    28  	DynamicRef         string             `json:"$dynamicRef,omitempty"`    // Reference to another schema that can be dynamically resolved.
    29  	Anchor             string             `json:"$anchor,omitempty"`        // Anchor for resolving relative JSON Pointers.
    30  	DynamicAnchor      string             `json:"$dynamicAnchor,omitempty"` // Anchor for dynamic resolution
    31  	Defs               map[string]*Schema `json:"$defs,omitempty"`          // An object containing schema definitions.
    32  	ResolvedRef        *Schema            `json:"-"`                        // Resolved schema for $ref
    33  	ResolvedDynamicRef *Schema            `json:"-"`                        // Resolved schema for $dynamicRef
    34  
    35  	// Boolean JSON Schemas, see https://json-schema.org/draft/2020-12/json-schema-core#name-boolean-json-schemas
    36  	Boolean *bool `json:"-"` // Boolean schema, used for quick validation.
    37  
    38  	// Applying subschemas with logical keywords, see https://json-schema.org/draft/2020-12/json-schema-core#name-keywords-for-applying-subsch
    39  	AllOf []*Schema `json:"allOf,omitempty"` // Array of schemas for validating the instance against all of them.
    40  	AnyOf []*Schema `json:"anyOf,omitempty"` // Array of schemas for validating the instance against any of them.
    41  	OneOf []*Schema `json:"oneOf,omitempty"` // Array of schemas for validating the instance against exactly one of them.
    42  	Not   *Schema   `json:"not,omitempty"`   // Schema for validating the instance against the negation of it.
    43  
    44  	// Applying subschemas conditionally, see https://json-schema.org/draft/2020-12/json-schema-core#name-keywords-for-applying-subsche
    45  	If               *Schema            `json:"if,omitempty"`               // Schema to be evaluated as a condition
    46  	Then             *Schema            `json:"then,omitempty"`             // Schema to be evaluated if 'if' is successful
    47  	Else             *Schema            `json:"else,omitempty"`             // Schema to be evaluated if 'if' is not successful
    48  	DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"` // Dependent schemas based on property presence
    49  
    50  	// Applying subschemas to array keywords, see https://json-schema.org/draft/2020-12/json-schema-core#name-keywords-for-applying-subschem
    51  	PrefixItems []*Schema `json:"prefixItems,omitempty"` // Array of schemas for validating the array items' prefix.
    52  	Items       *Schema   `json:"items,omitempty"`       // Schema for items in an array.
    53  	Contains    *Schema   `json:"contains,omitempty"`    // Schema for validating items in the array.
    54  
    55  	// Applying subschemas to objects keywords, see https://json-schema.org/draft/2020-12/json-schema-core#name-keywords-for-applying-subschemas
    56  	Properties           *SchemaMap `json:"properties,omitempty"`           // Definitions of properties for object types.
    57  	PatternProperties    *SchemaMap `json:"patternProperties,omitempty"`    // Definitions of properties for object types matched by specific patterns.
    58  	AdditionalProperties *Schema    `json:"additionalProperties,omitempty"` // Can be a boolean or a schema, controls additional properties handling.
    59  	PropertyNames        *Schema    `json:"propertyNames,omitempty"`        // Can be a boolean or a schema, controls property names validation.
    60  
    61  	// Any validation keywords, see https://json-schema.org/draft/2020-12/json-schema-validation#section-6.1
    62  	Type  SchemaType    `json:"type,omitempty"`  // Can be a single type or an array of types.
    63  	Enum  []interface{} `json:"enum,omitempty"`  // Enumerated values for the property.
    64  	Const *ConstValue   `json:"const,omitempty"` // Constant value for the property.
    65  
    66  	// Numeric validation keywords, see https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2
    67  	MultipleOf       *Rat `json:"multipleOf,omitempty"`       // Number must be a multiple of this value, strictly greater than 0.
    68  	Maximum          *Rat `json:"maximum,omitempty"`          // Maximum value of the number.
    69  	ExclusiveMaximum *Rat `json:"exclusiveMaximum,omitempty"` // Number must be less than this value.
    70  	Minimum          *Rat `json:"minimum,omitempty"`          // Minimum value of the number.
    71  	ExclusiveMinimum *Rat `json:"exclusiveMinimum,omitempty"` // Number must be greater than this value.
    72  
    73  	// String validation keywords, see https://json-schema.org/draft/2020-12/json-schema-validation#section-6.3
    74  	MaxLength *float64 `json:"maxLength,omitempty"` // Maximum length of a string.
    75  	MinLength *float64 `json:"minLength,omitempty"` // Minimum length of a string.
    76  	Pattern   *string  `json:"pattern,omitempty"`   // Regular expression pattern to match the string against.
    77  
    78  	// Array validation keywords, see https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4
    79  	MaxItems    *float64 `json:"maxItems,omitempty"`    // Maximum number of items in an array.
    80  	MinItems    *float64 `json:"minItems,omitempty"`    // Minimum number of items in an array.
    81  	UniqueItems *bool    `json:"uniqueItems,omitempty"` // Whether the items in the array must be unique.
    82  	MaxContains *float64 `json:"maxContains,omitempty"` // Maximum number of items in the array that can match the contains schema.
    83  	MinContains *float64 `json:"minContains,omitempty"` // Minimum number of items in the array that must match the contains schema.
    84  
    85  	// https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluateditems
    86  	UnevaluatedItems *Schema `json:"unevaluatedItems,omitempty"` // Schema for unevaluated items in an array.
    87  
    88  	// Object validation keywords, see https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5
    89  	MaxProperties     *float64            `json:"maxProperties,omitempty"`     // Maximum number of properties in an object.
    90  	MinProperties     *float64            `json:"minProperties,omitempty"`     // Minimum number of properties in an object.
    91  	Required          []string            `json:"required,omitempty"`          // List of required property names for object types.
    92  	DependentRequired map[string][]string `json:"dependentRequired,omitempty"` // Properties required when another property is present.
    93  
    94  	// https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties
    95  	UnevaluatedProperties *Schema `json:"unevaluatedProperties,omitempty"` // Schema for unevaluated properties in an object.
    96  
    97  	// Content validation keywords, see https://json-schema.org/draft/2020-12/json-schema-validation#name-a-vocabulary-for-the-conten
    98  	ContentEncoding  *string `json:"contentEncoding,omitempty"`  // Encoding format of the content.
    99  	ContentMediaType *string `json:"contentMediaType,omitempty"` // Media type of the content.
   100  	ContentSchema    *Schema `json:"contentSchema,omitempty"`    // Schema for validating the content.
   101  
   102  	// Meta-data for schema and instance description, see https://json-schema.org/draft/2020-12/json-schema-validation#name-a-vocabulary-for-basic-meta
   103  	Title       *string       `json:"title,omitempty"`       // A short summary of the schema.
   104  	Description *string       `json:"description,omitempty"` // A detailed description of the purpose of the schema.
   105  	Default     interface{}   `json:"default,omitempty"`     // Default value of the instance.
   106  	Deprecated  *bool         `json:"deprecated,omitempty"`  // Indicates that the schema is deprecated.
   107  	ReadOnly    *bool         `json:"readOnly,omitempty"`    // Indicates that the property is read-only.
   108  	WriteOnly   *bool         `json:"writeOnly,omitempty"`   // Indicates that the property is write-only.
   109  	Examples    []interface{} `json:"examples,omitempty"`    // Examples of the instance data that validates against this schema.
   110  }
   111  
   112  // newSchema parses JSON schema data and returns a Schema object.
   113  func newSchema(jsonSchema []byte) (*Schema, error) {
   114  	schema := &Schema{}
   115  
   116  	// Parse schema
   117  	if err := json.Unmarshal(jsonSchema, schema); err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	return schema, nil
   122  }
   123  
   124  // initializeSchema sets up the schema structure, resolves URIs, and initializes nested schemas.
   125  // It populates schema properties from the compiler settings and the parent schema context.
   126  func (s *Schema) initializeSchema(compiler *Compiler, parent *Schema) {
   127  	// Only set compiler if it's not nil (for constructor usage)
   128  	if compiler != nil {
   129  		s.compiler = compiler
   130  	}
   131  	s.parent = parent
   132  
   133  	// Get effective compiler for initialization
   134  	effectiveCompiler := s.GetCompiler()
   135  
   136  	parentBaseURI := s.getParentBaseURI()
   137  	if parentBaseURI == "" {
   138  		parentBaseURI = effectiveCompiler.DefaultBaseURI
   139  	}
   140  	if s.ID != "" {
   141  		if isValidURI(s.ID) {
   142  			s.uri = s.ID
   143  			s.baseURI = getBaseURI(s.ID)
   144  		} else {
   145  			resolvedURL := resolveRelativeURI(parentBaseURI, s.ID)
   146  			s.uri = resolvedURL
   147  			s.baseURI = getBaseURI(resolvedURL)
   148  		}
   149  	} else {
   150  		s.baseURI = parentBaseURI
   151  	}
   152  
   153  	if s.baseURI == "" {
   154  		if s.uri != "" && isValidURI(s.uri) {
   155  			s.baseURI = getBaseURI(s.uri)
   156  		}
   157  	}
   158  
   159  	if s.Anchor != "" {
   160  		s.setAnchor(s.Anchor)
   161  	}
   162  
   163  	if s.DynamicAnchor != "" {
   164  		s.setDynamicAnchor(s.DynamicAnchor)
   165  	}
   166  
   167  	if s.uri != "" && isValidURI(s.uri) {
   168  		root := s.getRootSchema()
   169  		root.setSchema(s.uri, s)
   170  	}
   171  
   172  	// For constructor usage (compiler=nil), don't pass compiler to children
   173  	// They should inherit through parent-child relationship via GetCompiler()
   174  	initializeNestedSchemas(s, compiler)
   175  	s.resolveReferences()
   176  }
   177  
   178  // initializeSchemaWithoutReferences sets up the schema structure without resolving references.
   179  // This is used by CompileBatch to defer reference resolution until all schemas are compiled.
   180  func (s *Schema) initializeSchemaWithoutReferences(compiler *Compiler, parent *Schema) {
   181  	// Only set compiler if it's not nil (for constructor usage)
   182  	if compiler != nil {
   183  		s.compiler = compiler
   184  	}
   185  	s.parent = parent
   186  
   187  	// Get effective compiler for initialization
   188  	effectiveCompiler := s.GetCompiler()
   189  
   190  	parentBaseURI := s.getParentBaseURI()
   191  	if parentBaseURI == "" {
   192  		parentBaseURI = effectiveCompiler.DefaultBaseURI
   193  	}
   194  	if s.ID != "" {
   195  		if isValidURI(s.ID) {
   196  			s.uri = s.ID
   197  			s.baseURI = getBaseURI(s.ID)
   198  		} else {
   199  			resolvedURL := resolveRelativeURI(parentBaseURI, s.ID)
   200  			s.uri = resolvedURL
   201  			s.baseURI = getBaseURI(resolvedURL)
   202  		}
   203  	} else {
   204  		s.baseURI = parentBaseURI
   205  	}
   206  
   207  	if s.baseURI == "" {
   208  		if s.uri != "" && isValidURI(s.uri) {
   209  			s.baseURI = getBaseURI(s.uri)
   210  		}
   211  	}
   212  
   213  	if s.Anchor != "" {
   214  		s.setAnchor(s.Anchor)
   215  	}
   216  
   217  	if s.DynamicAnchor != "" {
   218  		s.setDynamicAnchor(s.DynamicAnchor)
   219  	}
   220  
   221  	if s.uri != "" && isValidURI(s.uri) {
   222  		root := s.getRootSchema()
   223  		root.setSchema(s.uri, s)
   224  	}
   225  
   226  	// Initialize nested schemas but without resolving references
   227  	initializeNestedSchemasWithoutReferences(s, compiler)
   228  	// Note: We don't call s.resolveReferences() here - that's deferred
   229  }
   230  
   231  // initializeNestedSchemas initializes all nested or related schemas as defined in the structure.
   232  func initializeNestedSchemas(s *Schema, compiler *Compiler) {
   233  	if s.Defs != nil {
   234  		for _, def := range s.Defs {
   235  			def.initializeSchema(compiler, s)
   236  		}
   237  	}
   238  	// Initialize logical schema groupings
   239  	initializeSchemas(s.AllOf, compiler, s)
   240  	initializeSchemas(s.AnyOf, compiler, s)
   241  	initializeSchemas(s.OneOf, compiler, s)
   242  
   243  	// Initialize conditional schemas
   244  	if s.Not != nil {
   245  		s.Not.initializeSchema(compiler, s)
   246  	}
   247  	if s.If != nil {
   248  		s.If.initializeSchema(compiler, s)
   249  	}
   250  	if s.Then != nil {
   251  		s.Then.initializeSchema(compiler, s)
   252  	}
   253  	if s.Else != nil {
   254  		s.Else.initializeSchema(compiler, s)
   255  	}
   256  	if s.DependentSchemas != nil {
   257  		for _, depSchema := range s.DependentSchemas {
   258  			depSchema.initializeSchema(compiler, s)
   259  		}
   260  	}
   261  
   262  	// Initialize array and object schemas
   263  	if s.PrefixItems != nil {
   264  		for _, item := range s.PrefixItems {
   265  			item.initializeSchema(compiler, s)
   266  		}
   267  	}
   268  	if s.Items != nil {
   269  		s.Items.initializeSchema(compiler, s)
   270  	}
   271  	if s.Contains != nil {
   272  		s.Contains.initializeSchema(compiler, s)
   273  	}
   274  	if s.AdditionalProperties != nil {
   275  		s.AdditionalProperties.initializeSchema(compiler, s)
   276  	}
   277  	if s.Properties != nil {
   278  		for _, prop := range *s.Properties {
   279  			prop.initializeSchema(compiler, s)
   280  		}
   281  	}
   282  	if s.PatternProperties != nil {
   283  		for _, prop := range *s.PatternProperties {
   284  			prop.initializeSchema(compiler, s)
   285  		}
   286  	}
   287  	if s.UnevaluatedProperties != nil {
   288  		s.UnevaluatedProperties.initializeSchema(compiler, s)
   289  	}
   290  	if s.UnevaluatedItems != nil {
   291  		s.UnevaluatedItems.initializeSchema(compiler, s)
   292  	}
   293  	if s.ContentSchema != nil {
   294  		s.ContentSchema.initializeSchema(compiler, s)
   295  	}
   296  	if s.PropertyNames != nil {
   297  		s.PropertyNames.initializeSchema(compiler, s)
   298  	}
   299  }
   300  
   301  // initializeNestedSchemasWithoutReferences initializes all nested or related schemas
   302  // without resolving references. Used by CompileBatch.
   303  func initializeNestedSchemasWithoutReferences(s *Schema, compiler *Compiler) {
   304  	if s.Defs != nil {
   305  		for _, def := range s.Defs {
   306  			def.initializeSchemaWithoutReferences(compiler, s)
   307  		}
   308  	}
   309  	// Initialize logical schema groupings
   310  	initializeSchemasWithoutReferences(s.AllOf, compiler, s)
   311  	initializeSchemasWithoutReferences(s.AnyOf, compiler, s)
   312  	initializeSchemasWithoutReferences(s.OneOf, compiler, s)
   313  
   314  	// Initialize conditional schemas
   315  	if s.Not != nil {
   316  		s.Not.initializeSchemaWithoutReferences(compiler, s)
   317  	}
   318  	if s.If != nil {
   319  		s.If.initializeSchemaWithoutReferences(compiler, s)
   320  	}
   321  	if s.Then != nil {
   322  		s.Then.initializeSchemaWithoutReferences(compiler, s)
   323  	}
   324  	if s.Else != nil {
   325  		s.Else.initializeSchemaWithoutReferences(compiler, s)
   326  	}
   327  	if s.DependentSchemas != nil {
   328  		for _, depSchema := range s.DependentSchemas {
   329  			depSchema.initializeSchemaWithoutReferences(compiler, s)
   330  		}
   331  	}
   332  
   333  	// Initialize array and object schemas
   334  	if s.PrefixItems != nil {
   335  		for _, item := range s.PrefixItems {
   336  			item.initializeSchemaWithoutReferences(compiler, s)
   337  		}
   338  	}
   339  	if s.Items != nil {
   340  		s.Items.initializeSchemaWithoutReferences(compiler, s)
   341  	}
   342  	if s.Contains != nil {
   343  		s.Contains.initializeSchemaWithoutReferences(compiler, s)
   344  	}
   345  	if s.AdditionalProperties != nil {
   346  		s.AdditionalProperties.initializeSchemaWithoutReferences(compiler, s)
   347  	}
   348  	if s.Properties != nil {
   349  		for _, prop := range *s.Properties {
   350  			prop.initializeSchemaWithoutReferences(compiler, s)
   351  		}
   352  	}
   353  	if s.PatternProperties != nil {
   354  		for _, prop := range *s.PatternProperties {
   355  			prop.initializeSchemaWithoutReferences(compiler, s)
   356  		}
   357  	}
   358  	if s.UnevaluatedProperties != nil {
   359  		s.UnevaluatedProperties.initializeSchemaWithoutReferences(compiler, s)
   360  	}
   361  	if s.UnevaluatedItems != nil {
   362  		s.UnevaluatedItems.initializeSchemaWithoutReferences(compiler, s)
   363  	}
   364  	if s.ContentSchema != nil {
   365  		s.ContentSchema.initializeSchemaWithoutReferences(compiler, s)
   366  	}
   367  	if s.PropertyNames != nil {
   368  		s.PropertyNames.initializeSchemaWithoutReferences(compiler, s)
   369  	}
   370  }
   371  
   372  // setAnchor creates or updates the anchor mapping for the current schema and propagates it to parent schemas.
   373  func (s *Schema) setAnchor(anchor string) {
   374  	if s.anchors == nil {
   375  		s.anchors = make(map[string]*Schema)
   376  	}
   377  	s.anchors[anchor] = s
   378  
   379  	root := s.getRootSchema()
   380  	if root.anchors == nil {
   381  		root.anchors = make(map[string]*Schema)
   382  	}
   383  
   384  	if _, ok := root.anchors[anchor]; !ok {
   385  		root.anchors[anchor] = s
   386  	}
   387  }
   388  
   389  // setDynamicAnchor sets or updates a dynamic anchor for the current schema and propagates it to parents in the same scope.
   390  func (s *Schema) setDynamicAnchor(anchor string) {
   391  	if s.dynamicAnchors == nil {
   392  		s.dynamicAnchors = make(map[string]*Schema)
   393  	}
   394  	if _, ok := s.dynamicAnchors[anchor]; !ok {
   395  		s.dynamicAnchors[anchor] = s
   396  	}
   397  
   398  	scope := s.getScopeSchema()
   399  	if scope.dynamicAnchors == nil {
   400  		scope.dynamicAnchors = make(map[string]*Schema)
   401  	}
   402  
   403  	if _, ok := scope.dynamicAnchors[anchor]; !ok {
   404  		scope.dynamicAnchors[anchor] = s
   405  	}
   406  }
   407  
   408  // setSchema adds a schema to the internal schema cache, using the provided URI as the key.
   409  func (s *Schema) setSchema(uri string, schema *Schema) *Schema {
   410  	if s.schemas == nil {
   411  		s.schemas = make(map[string]*Schema)
   412  	}
   413  
   414  	s.schemas[uri] = schema
   415  	return s
   416  }
   417  
   418  func (s *Schema) getSchema(ref string) (*Schema, error) {
   419  	baseURI, anchor := splitRef(ref)
   420  
   421  	if schema, exists := s.schemas[baseURI]; exists {
   422  		if baseURI == ref {
   423  			return schema, nil
   424  		}
   425  		return schema.resolveAnchor(anchor)
   426  	}
   427  
   428  	return nil, ErrFailedToResolveReference
   429  }
   430  
   431  // initializeSchemas iteratively initializes a list of nested schemas.
   432  func initializeSchemas(schemas []*Schema, compiler *Compiler, parent *Schema) {
   433  	for _, schema := range schemas {
   434  		if schema != nil {
   435  			schema.initializeSchema(compiler, parent)
   436  		}
   437  	}
   438  }
   439  
   440  // initializeSchemasWithoutReferences iteratively initializes a list of nested schemas
   441  // without resolving references. Used by CompileBatch.
   442  func initializeSchemasWithoutReferences(schemas []*Schema, compiler *Compiler, parent *Schema) {
   443  	for _, schema := range schemas {
   444  		if schema != nil {
   445  			schema.initializeSchemaWithoutReferences(compiler, parent)
   446  		}
   447  	}
   448  }
   449  
   450  // GetSchemaURI returns the resolved URI for the schema, or an empty string if no URI is defined.
   451  func (s *Schema) GetSchemaURI() string {
   452  	if s.uri != "" {
   453  		return s.uri
   454  	}
   455  	root := s.getRootSchema()
   456  	if root.uri != "" {
   457  		return root.uri
   458  	}
   459  
   460  	return ""
   461  }
   462  
   463  func (s *Schema) GetSchemaLocation(anchor string) string {
   464  	uri := s.GetSchemaURI()
   465  
   466  	return uri + "#" + anchor
   467  }
   468  
   469  // getRootSchema returns the highest-level parent schema, serving as the root in the schema tree.
   470  func (s *Schema) getRootSchema() *Schema {
   471  	if s.parent != nil {
   472  		return s.parent.getRootSchema()
   473  	}
   474  
   475  	return s
   476  }
   477  
   478  func (s *Schema) getScopeSchema() *Schema {
   479  	if s.ID != "" {
   480  		return s
   481  	} else if s.parent != nil {
   482  		return s.parent.getScopeSchema()
   483  	}
   484  
   485  	return s
   486  }
   487  
   488  // getParentBaseURI returns the base URI from the nearest parent schema that has one defined,
   489  // or an empty string if none of the parents up to the root define a base URI.
   490  func (s *Schema) getParentBaseURI() string {
   491  	for p := s.parent; p != nil; p = p.parent {
   492  		if p.baseURI != "" {
   493  			return p.baseURI
   494  		}
   495  	}
   496  	return ""
   497  }
   498  
   499  // MarshalJSON implements json.Marshaler
   500  func (s *Schema) MarshalJSON() ([]byte, error) {
   501  	if s.Boolean != nil {
   502  		return json.Marshal(s.Boolean)
   503  	}
   504  
   505  	// Marshal as a normal struct
   506  	type Alias Schema
   507  	return json.Marshal((*Alias)(s))
   508  }
   509  
   510  // UnmarshalJSON handles unmarshaling JSON data into the Schema type.
   511  func (s *Schema) UnmarshalJSON(data []byte) error {
   512  	// First try to parse as a boolean
   513  	var b bool
   514  	if err := json.Unmarshal(data, &b); err == nil {
   515  		s.Boolean = &b
   516  		return nil
   517  	}
   518  
   519  	// If not a boolean, parse as a normal struct
   520  	type Alias Schema
   521  	var alias Alias
   522  	if err := json.Unmarshal(data, &alias); err != nil {
   523  		return err
   524  	}
   525  	*s = Schema(alias)
   526  
   527  	// Special handling for the const field
   528  	var raw map[string]json.RawMessage
   529  	if err := json.Unmarshal(data, &raw); err != nil {
   530  		return err
   531  	}
   532  	if constData, ok := raw["const"]; ok {
   533  		if s.Const == nil {
   534  			s.Const = &ConstValue{}
   535  		}
   536  		return s.Const.UnmarshalJSON(constData)
   537  	}
   538  
   539  	return nil
   540  }
   541  
   542  // SchemaMap represents a map of string keys to *Schema values, used primarily for properties and patternProperties.
   543  type SchemaMap map[string]*Schema
   544  
   545  // MarshalJSON ensures that SchemaMap serializes properly as a JSON object.
   546  func (sm SchemaMap) MarshalJSON() ([]byte, error) {
   547  	m := make(map[string]*Schema)
   548  	for k, v := range sm {
   549  		m[k] = v
   550  	}
   551  	return json.Marshal(m)
   552  }
   553  
   554  // UnmarshalJSON ensures that JSON objects are correctly parsed into SchemaMap,
   555  // supporting the detailed structure required for nested schema definitions.
   556  func (sm *SchemaMap) UnmarshalJSON(data []byte) error {
   557  	m := make(map[string]*Schema)
   558  	if err := json.Unmarshal(data, &m); err != nil {
   559  		return err
   560  	}
   561  	*sm = SchemaMap(m)
   562  	return nil
   563  }
   564  
   565  // SchemaType holds a set of SchemaType values, accommodating complex schema definitions that permit multiple types.
   566  type SchemaType []string
   567  
   568  // MarshalJSON customizes the JSON serialization of SchemaType.
   569  func (r SchemaType) MarshalJSON() ([]byte, error) {
   570  	if len(r) == 1 {
   571  		return json.Marshal(r[0])
   572  	}
   573  	return json.Marshal([]string(r))
   574  }
   575  
   576  // UnmarshalJSON customizes the JSON deserialization into SchemaType.
   577  func (r *SchemaType) UnmarshalJSON(data []byte) error {
   578  	var singleType string
   579  	if err := json.Unmarshal(data, &singleType); err == nil {
   580  		*r = SchemaType{singleType}
   581  		return nil
   582  	}
   583  
   584  	var multiType []string
   585  	if err := json.Unmarshal(data, &multiType); err == nil {
   586  		*r = SchemaType(multiType)
   587  		return nil
   588  	}
   589  
   590  	return ErrInvalidJSONSchemaType
   591  }
   592  
   593  // ConstValue represents a constant value in a JSON Schema.
   594  type ConstValue struct {
   595  	Value interface{}
   596  	IsSet bool
   597  }
   598  
   599  // UnmarshalJSON handles unmarshaling a JSON value into the ConstValue type.
   600  func (cv *ConstValue) UnmarshalJSON(data []byte) error {
   601  	// Ensure cv is not nil
   602  	if cv == nil {
   603  		return ErrNilConstValue
   604  	}
   605  
   606  	// Set IsSet to true because we are setting a value
   607  	cv.IsSet = true
   608  
   609  	// If the input is "null", explicitly set Value to nil
   610  	if string(data) == "null" {
   611  		cv.Value = nil
   612  		return nil
   613  	}
   614  
   615  	// Otherwise parse the value normally
   616  	return json.Unmarshal(data, &cv.Value)
   617  }
   618  
   619  // MarshalJSON handles marshaling the ConstValue type back to JSON.
   620  func (cv ConstValue) MarshalJSON() ([]byte, error) {
   621  	if !cv.IsSet {
   622  		return []byte("null"), nil
   623  	}
   624  	if cv.Value == nil {
   625  		return []byte("null"), nil
   626  	}
   627  	return json.Marshal(cv.Value)
   628  }
   629  
   630  // SetCompiler sets a custom Compiler for the Schema and returns the Schema itself to support method chaining
   631  func (s *Schema) SetCompiler(compiler *Compiler) *Schema {
   632  	s.compiler = compiler
   633  	return s
   634  }
   635  
   636  // GetCompiler gets the effective Compiler for the Schema
   637  // Lookup order: current Schema -> parent Schema -> defaultCompiler
   638  func (s *Schema) GetCompiler() *Compiler {
   639  	if s.compiler != nil {
   640  		return s.compiler
   641  	}
   642  
   643  	// Look up parent Schema's compiler
   644  	if s.parent != nil {
   645  		return s.parent.GetCompiler()
   646  	}
   647  
   648  	return defaultCompiler
   649  }