github.com/kaptinlin/jsonschema@v0.4.6/unmarshal.go (about) 1 package jsonschema 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "time" 8 ) 9 10 // Static errors for better error handling 11 var ( 12 ErrTypeConversion = errors.New("type conversion failed") 13 ErrTimeParseFailure = errors.New("failed to parse time string") 14 ErrTimeTypeConversion = errors.New("cannot convert to time.Time") 15 ErrNilDestination = errors.New("destination cannot be nil") 16 ErrNotPointer = errors.New("destination must be a pointer") 17 ErrNilPointer = errors.New("destination pointer cannot be nil") 18 ) 19 20 // UnmarshalError represents an error that occurred during unmarshaling 21 type UnmarshalError struct { 22 Type string 23 Field string 24 Reason string 25 Err error 26 } 27 28 func (e *UnmarshalError) Error() string { 29 if e.Field != "" { 30 return fmt.Sprintf("unmarshal error at field '%s': %s", e.Field, e.Reason) 31 } 32 return fmt.Sprintf("unmarshal error: %s", e.Reason) 33 } 34 35 func (e *UnmarshalError) Unwrap() error { 36 return e.Err 37 } 38 39 // Unmarshal unmarshals data into dst, applying default values from the schema. 40 // This method does NOT perform validation - use Validate() separately for validation. 41 // 42 // Supported source types: 43 // - []byte (JSON data - automatically parsed if valid JSON) 44 // - map[string]interface{} (parsed JSON object) 45 // - Go structs and other types 46 // 47 // Supported destination types: 48 // - *struct (Go struct pointer) 49 // - *map[string]interface{} (map pointer) 50 // - other pointer types (via JSON marshaling) 51 // 52 // Example usage: 53 // 54 // result := schema.Validate(data) 55 // if result.IsValid() { 56 // err := schema.Unmarshal(&user, data) 57 // if err != nil { 58 // log.Fatal(err) 59 // } 60 // } else { 61 // // Handle validation errors 62 // for field, err := range result.Errors { 63 // log.Printf("%s: %s", field, err.Message) 64 // } 65 // } 66 // 67 // To use JSON strings, convert them to []byte first: 68 // 69 // schema.Unmarshal(&target, []byte(jsonString)) 70 func (s *Schema) Unmarshal(dst, src interface{}) error { 71 if err := s.validateDestination(dst); err != nil { 72 return err 73 } 74 75 // Convert source to the appropriate intermediate format for processing 76 intermediate, isObject, err := s.convertSource(src) 77 if err != nil { 78 return &UnmarshalError{Type: "source", Reason: "failed to convert source", Err: err} 79 } 80 81 if isObject { 82 return s.unmarshalObject(dst, intermediate) 83 } 84 return s.unmarshalNonObject(dst, intermediate) 85 } 86 87 // validateDestination validates the destination parameter 88 func (s *Schema) validateDestination(dst interface{}) error { 89 if dst == nil { 90 return &UnmarshalError{Type: "destination", Reason: ErrNilDestination.Error()} 91 } 92 93 dstVal := reflect.ValueOf(dst) 94 if dstVal.Kind() != reflect.Ptr { 95 return &UnmarshalError{Type: "destination", Reason: ErrNotPointer.Error()} 96 } 97 98 if dstVal.IsNil() { 99 return &UnmarshalError{Type: "destination", Reason: ErrNilPointer.Error()} 100 } 101 102 return nil 103 } 104 105 // unmarshalObject handles object type unmarshaling with defaults but NO validation 106 func (s *Schema) unmarshalObject(dst, intermediate interface{}) error { 107 objData, ok := intermediate.(map[string]interface{}) 108 if !ok { 109 return &UnmarshalError{Type: "source", Reason: "expected object but got different type"} 110 } 111 112 // Apply default values 113 if err := s.applyDefaults(objData, s); err != nil { 114 return &UnmarshalError{Type: "defaults", Reason: "failed to apply defaults", Err: err} 115 } 116 117 // NO validation - unmarshal directly 118 return s.unmarshalToDestination(dst, objData) 119 } 120 121 // unmarshalNonObject handles non-object type unmarshaling without validation 122 func (s *Schema) unmarshalNonObject(dst, intermediate interface{}) error { 123 // No validation for non-object types, use JSON marshaling directly 124 jsonData, err := s.GetCompiler().jsonEncoder(intermediate) 125 if err != nil { 126 return &UnmarshalError{Type: "marshal", Reason: "failed to encode intermediate data", Err: err} 127 } 128 129 if err := s.GetCompiler().jsonDecoder(jsonData, dst); err != nil { 130 return &UnmarshalError{Type: "unmarshal", Reason: "failed to decode to destination", Err: err} 131 } 132 133 return nil 134 } 135 136 // convertSource converts various source types to intermediate format for processing 137 // Returns (data, isObject, error) where isObject indicates if the result is a JSON object 138 func (s *Schema) convertSource(src interface{}) (interface{}, bool, error) { 139 switch v := src.(type) { 140 case []byte: 141 return s.convertBytesSource(v) 142 case map[string]interface{}: 143 // Create a deep copy to avoid modifying the original 144 return deepCopyMap(v), true, nil 145 default: 146 return s.convertGenericSource(v) 147 } 148 } 149 150 // convertBytesSource handles []byte input with JSON parsing 151 func (s *Schema) convertBytesSource(data []byte) (interface{}, bool, error) { 152 var parsed interface{} 153 if err := s.GetCompiler().jsonDecoder(data, &parsed); err == nil { 154 // Successfully parsed as JSON, check if it's an object 155 if objData, ok := parsed.(map[string]interface{}); ok { 156 return objData, true, nil 157 } 158 // Non-object JSON (array, string, number, boolean, null) 159 return parsed, false, nil 160 } else { 161 // Only return error if it looks like it was meant to be JSON 162 if len(data) > 0 && (data[0] == '{' || data[0] == '[') { 163 return nil, false, fmt.Errorf("failed to decode JSON: %w", err) 164 } 165 // Otherwise, treat as raw bytes 166 return data, false, nil 167 } 168 } 169 170 // convertGenericSource handles structs and other types 171 func (s *Schema) convertGenericSource(src interface{}) (interface{}, bool, error) { 172 // Handle structs and other types 173 // First try to see if it's already a map (for interface{} containing map) 174 if objData, ok := src.(map[string]interface{}); ok { 175 return deepCopyMap(objData), true, nil 176 } 177 178 // For other types, use JSON round-trip to convert 179 data, err := s.GetCompiler().jsonEncoder(src) 180 if err != nil { 181 return nil, false, fmt.Errorf("failed to encode source: %w", err) 182 } 183 184 var parsed interface{} 185 if err := s.GetCompiler().jsonDecoder(data, &parsed); err != nil { 186 return nil, false, fmt.Errorf("failed to decode intermediate JSON: %w", err) 187 } 188 189 // Check if the result is an object 190 if objData, ok := parsed.(map[string]interface{}); ok { 191 return objData, true, nil 192 } 193 194 return parsed, false, nil 195 } 196 197 // applyDefaults recursively applies default values from schema to data 198 func (s *Schema) applyDefaults(data map[string]interface{}, schema *Schema) error { 199 if schema == nil || schema.Properties == nil { 200 return nil 201 } 202 203 // Apply defaults for current level properties 204 for propName, propSchema := range *schema.Properties { 205 if err := s.applyPropertyDefaults(data, propName, propSchema); err != nil { 206 return fmt.Errorf("failed to apply defaults for property '%s': %w", propName, err) 207 } 208 } 209 210 return nil 211 } 212 213 // applyPropertyDefaults applies defaults for a single property 214 func (s *Schema) applyPropertyDefaults(data map[string]interface{}, propName string, propSchema *Schema) error { 215 // Set default value if property doesn't exist 216 if _, exists := data[propName]; !exists && propSchema.Default != nil { 217 // Try to evaluate dynamic default value 218 defaultValue, err := s.evaluateDefaultValue(propSchema.Default) 219 if err != nil { 220 return fmt.Errorf("failed to evaluate default value for property '%s': %w", propName, err) 221 } 222 data[propName] = defaultValue 223 } 224 225 propData, exists := data[propName] 226 if !exists { 227 return nil 228 } 229 230 // Recursively apply defaults for nested objects 231 if objData, ok := propData.(map[string]interface{}); ok { 232 return s.applyDefaults(objData, propSchema) 233 } 234 235 // Handle arrays 236 if arrayData, ok := propData.([]interface{}); ok && propSchema.Items != nil { 237 return s.applyArrayDefaults(arrayData, propSchema.Items, propName) 238 } 239 240 return nil 241 } 242 243 // evaluateDefaultValue evaluates a default value, checking if it's a function call 244 func (s *Schema) evaluateDefaultValue(defaultValue interface{}) (interface{}, error) { 245 // Check if it's a string that might be a function call 246 defaultStr, ok := defaultValue.(string) 247 if !ok { 248 // Non-string default value, return as is 249 return defaultValue, nil 250 } 251 252 // Try to parse as function call 253 call, err := parseFunctionCall(defaultStr) 254 if err != nil { 255 return nil, fmt.Errorf("failed to parse function call: %w", err) 256 } 257 258 if call == nil { 259 // Not a function call, use literal value 260 return defaultStr, nil 261 } 262 263 // Get the effective compiler (current schema -> parent schema -> defaultCompiler) 264 compiler := s.GetCompiler() 265 if compiler == nil { 266 // No compiler available, use literal value as fallback 267 return defaultStr, nil 268 } 269 270 // Look up and execute function 271 fn, exists := compiler.getDefaultFunc(call.Name) 272 if !exists { 273 // Function not registered, use literal value as fallback 274 return defaultStr, nil 275 } 276 277 // Execute function 278 value, err := fn(call.Args...) 279 if err != nil { 280 // Execution failed, use literal value as fallback 281 return defaultStr, nil //nolint:nilerr // Intentional fallback to literal value on function execution failure 282 } 283 284 return value, nil 285 } 286 287 // applyArrayDefaults applies defaults for array items 288 func (s *Schema) applyArrayDefaults(arrayData []interface{}, itemSchema *Schema, propName string) error { 289 for _, item := range arrayData { 290 if itemMap, ok := item.(map[string]interface{}); ok { 291 if err := s.applyDefaults(itemMap, itemSchema); err != nil { 292 return fmt.Errorf("failed to apply defaults for array item in '%s': %w", propName, err) 293 } 294 } 295 } 296 return nil 297 } 298 299 // unmarshalToDestination converts the processed map to the destination type 300 func (s *Schema) unmarshalToDestination(dst interface{}, data map[string]interface{}) error { 301 dstVal := reflect.ValueOf(dst).Elem() 302 303 //nolint:exhaustive // Only handling Map, Struct, and Ptr kinds - other types use default fallback 304 switch dstVal.Kind() { 305 case reflect.Map: 306 return s.unmarshalToMap(dstVal, data) 307 case reflect.Struct: 308 return s.unmarshalToStruct(dstVal, data) 309 case reflect.Ptr: 310 if dstVal.IsNil() { 311 dstVal.Set(reflect.New(dstVal.Type().Elem())) 312 } 313 return s.unmarshalToDestination(dstVal.Interface(), data) 314 default: 315 // Fallback to JSON marshaling/unmarshaling for other types 316 return s.unmarshalViaJSON(dst, data) 317 } 318 } 319 320 // unmarshalViaJSON uses JSON round-trip for unsupported types 321 func (s *Schema) unmarshalViaJSON(dst interface{}, data map[string]interface{}) error { 322 jsonData, err := s.GetCompiler().jsonEncoder(data) 323 if err != nil { 324 return fmt.Errorf("failed to encode data for fallback: %w", err) 325 } 326 return s.GetCompiler().jsonDecoder(jsonData, dst) 327 } 328 329 // unmarshalToMap converts data to a map destination 330 func (s *Schema) unmarshalToMap(dstVal reflect.Value, data map[string]interface{}) error { 331 if dstVal.IsNil() { 332 dstVal.Set(reflect.MakeMap(dstVal.Type())) 333 } 334 335 for key, value := range data { 336 keyVal := reflect.ValueOf(key) 337 valueVal := reflect.ValueOf(value) 338 339 // Convert value type if necessary 340 if valueVal.IsValid() && valueVal.Type().ConvertibleTo(dstVal.Type().Elem()) { 341 valueVal = valueVal.Convert(dstVal.Type().Elem()) 342 } 343 344 dstVal.SetMapIndex(keyVal, valueVal) 345 } 346 347 return nil 348 } 349 350 // unmarshalToStruct converts data to a struct destination 351 func (s *Schema) unmarshalToStruct(dstVal reflect.Value, data map[string]interface{}) error { 352 structType := dstVal.Type() 353 fieldCache := getFieldCache(structType) 354 355 for jsonName, value := range data { 356 fieldInfo, exists := fieldCache.FieldsByName[jsonName] 357 if !exists { 358 continue // Skip unknown fields 359 } 360 361 fieldVal := dstVal.Field(fieldInfo.Index) 362 if !fieldVal.CanSet() { 363 continue // Skip unexported fields 364 } 365 366 if err := s.setFieldValue(fieldVal, value); err != nil { 367 return fmt.Errorf("failed to set field '%s': %w", jsonName, err) 368 } 369 } 370 371 return nil 372 } 373 374 // setFieldValue sets a struct field value with type conversion 375 func (s *Schema) setFieldValue(fieldVal reflect.Value, value interface{}) error { 376 if value == nil { 377 return s.setNilValue(fieldVal) 378 } 379 380 valueVal := reflect.ValueOf(value) 381 fieldType := fieldVal.Type() 382 383 // Handle pointer fields 384 if fieldType.Kind() == reflect.Ptr { 385 return s.setPointerValue(fieldVal, valueVal, fieldType) 386 } 387 388 // Direct assignment for compatible types 389 if valueVal.Type().AssignableTo(fieldType) { 390 fieldVal.Set(valueVal) 391 return nil 392 } 393 394 // Type conversion for compatible types 395 if valueVal.Type().ConvertibleTo(fieldType) { 396 fieldVal.Set(valueVal.Convert(fieldType)) 397 return nil 398 } 399 400 // Special handling for time.Time 401 if fieldType == reflect.TypeOf(time.Time{}) { 402 return s.setTimeValue(fieldVal, value) 403 } 404 405 // Handle nested structs and maps 406 if fieldType.Kind() == reflect.Struct || fieldType.Kind() == reflect.Map { 407 return s.setComplexValue(fieldVal, value) 408 } 409 410 return fmt.Errorf("%w: cannot convert %T to %v", ErrTypeConversion, value, fieldType) 411 } 412 413 // setNilValue handles nil value assignment 414 func (s *Schema) setNilValue(fieldVal reflect.Value) error { 415 if fieldVal.Kind() == reflect.Ptr { 416 fieldVal.Set(reflect.Zero(fieldVal.Type())) 417 } 418 return nil 419 } 420 421 // setPointerValue handles pointer field assignment 422 func (s *Schema) setPointerValue(fieldVal reflect.Value, valueVal reflect.Value, fieldType reflect.Type) error { 423 if valueVal.Type().ConvertibleTo(fieldType.Elem()) { 424 newVal := reflect.New(fieldType.Elem()) 425 newVal.Elem().Set(valueVal.Convert(fieldType.Elem())) 426 fieldVal.Set(newVal) 427 return nil 428 } 429 430 if fieldVal.IsNil() { 431 fieldVal.Set(reflect.New(fieldType.Elem())) 432 } 433 return s.setFieldValue(fieldVal.Elem(), valueVal.Interface()) 434 } 435 436 // setComplexValue handles nested structs and maps 437 func (s *Schema) setComplexValue(fieldVal reflect.Value, value interface{}) error { 438 jsonData, err := s.GetCompiler().jsonEncoder(value) 439 if err != nil { 440 return fmt.Errorf("failed to encode nested value: %w", err) 441 } 442 return s.GetCompiler().jsonDecoder(jsonData, fieldVal.Addr().Interface()) 443 } 444 445 // setTimeValue handles time.Time field assignment from various string formats 446 func (s *Schema) setTimeValue(fieldVal reflect.Value, value interface{}) error { 447 switch v := value.(type) { 448 case string: 449 return s.parseTimeString(fieldVal, v) 450 case time.Time: 451 fieldVal.Set(reflect.ValueOf(v)) 452 return nil 453 default: 454 return fmt.Errorf("%w: %T", ErrTimeTypeConversion, value) 455 } 456 } 457 458 // parseTimeString parses time string in various formats 459 func (s *Schema) parseTimeString(fieldVal reflect.Value, timeStr string) error { 460 // Try multiple time formats 461 formats := []string{ 462 time.RFC3339, 463 time.RFC3339Nano, 464 "2006-01-02T15:04:05Z", 465 "2006-01-02 15:04:05", 466 "2006-01-02", 467 } 468 469 for _, format := range formats { 470 if t, err := time.Parse(format, timeStr); err == nil { 471 fieldVal.Set(reflect.ValueOf(t)) 472 return nil 473 } 474 } 475 return fmt.Errorf("%w: %s", ErrTimeParseFailure, timeStr) 476 } 477 478 // deepCopyMap creates a deep copy of a map[string]interface{} 479 func deepCopyMap(original map[string]interface{}) map[string]interface{} { 480 copy := make(map[string]interface{}, len(original)) 481 for key, value := range original { 482 switch v := value.(type) { 483 case map[string]interface{}: 484 copy[key] = deepCopyMap(v) 485 case []interface{}: 486 copy[key] = deepCopySlice(v) 487 default: 488 copy[key] = value 489 } 490 } 491 return copy 492 } 493 494 // deepCopySlice creates a deep copy of a []interface{} 495 func deepCopySlice(original []interface{}) []interface{} { 496 copy := make([]interface{}, len(original)) 497 for i, value := range original { 498 switch v := value.(type) { 499 case map[string]interface{}: 500 copy[i] = deepCopyMap(v) 501 case []interface{}: 502 copy[i] = deepCopySlice(v) 503 default: 504 copy[i] = value 505 } 506 } 507 return copy 508 }