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  }