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 }