Skip to content

Commit 3a992be

Browse files
committed
add: FlexibleUnmarshal function for versatile JSON handling
- Introduced FlexibleUnmarshal to enable flexible processing of JSON responses - Integrated FlexibleUnmarshal into existing doRequest function for improved robustness - Implemented FlexibleJSONUnmarshal helper function - Added unit tests to verify flexible JSON unmarshalling behavior
1 parent fce4eea commit 3a992be

File tree

4 files changed

+423
-1
lines changed

4 files changed

+423
-1
lines changed

client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func (c *Client) doRequest(ctx context.Context, method, endpoint string, body in
164164
}
165165

166166
if result != nil {
167-
if err := json.Unmarshal(respBody, result); err != nil {
167+
if err := FlexibleUnmarshal(respBody, result); err != nil {
168168
return fmt.Errorf("failed to unmarshal response: %w", err)
169169
}
170170
}

utils.go

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,291 @@ func mergeObjects(obj1, obj2 map[string]interface{}) map[string]interface{} {
237237
return merged
238238
}
239239

240+
// FlexibleUnmarshal performs flexible JSON unmarshal that handles type mismatches
241+
func FlexibleUnmarshal(data []byte, v interface{}) error {
242+
// First try normal unmarshal
243+
if err := json.Unmarshal(data, v); err == nil {
244+
return nil
245+
}
246+
247+
// If normal unmarshal fails, try flexible unmarshal
248+
var rawData map[string]interface{}
249+
if err := json.Unmarshal(data, &rawData); err != nil {
250+
return err
251+
}
252+
253+
return flexibleMapToStruct(rawData, v)
254+
}
255+
256+
// flexibleMapToStruct converts map to struct with flexible type handling
257+
func flexibleMapToStruct(data map[string]interface{}, v interface{}) error {
258+
rv := reflect.ValueOf(v)
259+
if rv.Kind() != reflect.Ptr || rv.IsNil() {
260+
return fmt.Errorf("destination must be a non-nil pointer")
261+
}
262+
263+
rv = rv.Elem()
264+
if rv.Kind() != reflect.Struct {
265+
return fmt.Errorf("destination must be a pointer to struct")
266+
}
267+
268+
rt := rv.Type()
269+
for i := 0; i < rv.NumField(); i++ {
270+
field := rv.Field(i)
271+
fieldType := rt.Field(i)
272+
273+
if !field.CanSet() {
274+
continue
275+
}
276+
277+
jsonTag := fieldType.Tag.Get("json")
278+
if jsonTag == "" || jsonTag == "-" {
279+
continue
280+
}
281+
282+
tagParts := strings.Split(jsonTag, ",")
283+
fieldName := tagParts[0]
284+
285+
value, exists := data[fieldName]
286+
if !exists {
287+
continue
288+
}
289+
290+
if err := setFlexibleValue(field, value); err != nil {
291+
return fmt.Errorf("error setting field %s: %v", fieldName, err)
292+
}
293+
}
294+
295+
return nil
296+
}
297+
298+
// setFlexibleValue sets value to field with flexible type conversion
299+
func setFlexibleValue(field reflect.Value, value interface{}) error {
300+
if value == nil {
301+
return nil
302+
}
303+
304+
targetType := field.Type()
305+
sourceValue := reflect.ValueOf(value)
306+
307+
// Handle pointer types
308+
if targetType.Kind() == reflect.Ptr {
309+
if field.IsNil() {
310+
field.Set(reflect.New(targetType.Elem()))
311+
}
312+
return setFlexibleValue(field.Elem(), value)
313+
}
314+
315+
// Handle same types
316+
if sourceValue.Type().AssignableTo(targetType) {
317+
field.Set(sourceValue)
318+
return nil
319+
}
320+
321+
// Handle conversions
322+
switch targetType.Kind() {
323+
case reflect.String:
324+
return setStringValue(field, value)
325+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
326+
return setIntValue(field, value)
327+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
328+
return setUintValue(field, value)
329+
case reflect.Float32, reflect.Float64:
330+
return setFloatValue(field, value)
331+
case reflect.Bool:
332+
return setBoolValue(field, value)
333+
case reflect.Slice:
334+
return setSliceValue(field, value)
335+
case reflect.Struct:
336+
return setStructValue(field, value)
337+
default:
338+
return fmt.Errorf("unsupported type conversion from %T to %s", value, targetType)
339+
}
340+
}
341+
342+
// setStringValue converts any value to string
343+
func setStringValue(field reflect.Value, value interface{}) error {
344+
switch v := value.(type) {
345+
case string:
346+
field.SetString(v)
347+
case int, int8, int16, int32, int64:
348+
field.SetString(fmt.Sprintf("%d", v))
349+
case uint, uint8, uint16, uint32, uint64:
350+
field.SetString(fmt.Sprintf("%d", v))
351+
case float32, float64:
352+
field.SetString(fmt.Sprintf("%g", v))
353+
case bool:
354+
field.SetString(fmt.Sprintf("%t", v))
355+
default:
356+
field.SetString(fmt.Sprintf("%v", v))
357+
}
358+
return nil
359+
}
360+
361+
// setIntValue converts any value to int
362+
func setIntValue(field reflect.Value, value interface{}) error {
363+
switch v := value.(type) {
364+
case int:
365+
field.SetInt(int64(v))
366+
case int8:
367+
field.SetInt(int64(v))
368+
case int16:
369+
field.SetInt(int64(v))
370+
case int32:
371+
field.SetInt(int64(v))
372+
case int64:
373+
field.SetInt(v)
374+
case uint, uint8, uint16, uint32, uint64:
375+
field.SetInt(int64(reflect.ValueOf(v).Uint()))
376+
case float32:
377+
field.SetInt(int64(v))
378+
case float64:
379+
field.SetInt(int64(v))
380+
case string:
381+
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
382+
field.SetInt(i)
383+
} else if f, err := strconv.ParseFloat(v, 64); err == nil {
384+
field.SetInt(int64(f))
385+
} else {
386+
return fmt.Errorf("cannot convert string %q to int", v)
387+
}
388+
default:
389+
return fmt.Errorf("cannot convert %T to int", v)
390+
}
391+
return nil
392+
}
393+
394+
// setUintValue converts any value to uint
395+
func setUintValue(field reflect.Value, value interface{}) error {
396+
switch v := value.(type) {
397+
case uint:
398+
field.SetUint(uint64(v))
399+
case uint8:
400+
field.SetUint(uint64(v))
401+
case uint16:
402+
field.SetUint(uint64(v))
403+
case uint32:
404+
field.SetUint(uint64(v))
405+
case uint64:
406+
field.SetUint(v)
407+
case int, int8, int16, int32, int64:
408+
val := reflect.ValueOf(v).Int()
409+
if val < 0 {
410+
return fmt.Errorf("cannot convert negative int %d to uint", val)
411+
}
412+
field.SetUint(uint64(val))
413+
case float32:
414+
if v < 0 {
415+
return fmt.Errorf("cannot convert negative float %f to uint", v)
416+
}
417+
field.SetUint(uint64(v))
418+
case float64:
419+
if v < 0 {
420+
return fmt.Errorf("cannot convert negative float %f to uint", v)
421+
}
422+
field.SetUint(uint64(v))
423+
case string:
424+
if i, err := strconv.ParseUint(v, 10, 64); err == nil {
425+
field.SetUint(i)
426+
} else if f, err := strconv.ParseFloat(v, 64); err == nil {
427+
if f < 0 {
428+
return fmt.Errorf("cannot convert negative float %f to uint", f)
429+
}
430+
field.SetUint(uint64(f))
431+
} else {
432+
return fmt.Errorf("cannot convert string %q to uint", v)
433+
}
434+
default:
435+
return fmt.Errorf("cannot convert %T to uint", v)
436+
}
437+
return nil
438+
}
439+
440+
// setFloatValue converts any value to float
441+
func setFloatValue(field reflect.Value, value interface{}) error {
442+
switch v := value.(type) {
443+
case float32:
444+
field.SetFloat(float64(v))
445+
case float64:
446+
field.SetFloat(v)
447+
case int, int8, int16, int32, int64:
448+
field.SetFloat(float64(reflect.ValueOf(v).Int()))
449+
case uint, uint8, uint16, uint32, uint64:
450+
field.SetFloat(float64(reflect.ValueOf(v).Uint()))
451+
case string:
452+
if f, err := strconv.ParseFloat(v, 64); err == nil {
453+
field.SetFloat(f)
454+
} else {
455+
return fmt.Errorf("cannot convert string %q to float", v)
456+
}
457+
default:
458+
return fmt.Errorf("cannot convert %T to float", v)
459+
}
460+
return nil
461+
}
462+
463+
// setBoolValue converts any value to bool
464+
func setBoolValue(field reflect.Value, value interface{}) error {
465+
switch v := value.(type) {
466+
case bool:
467+
field.SetBool(v)
468+
case string:
469+
if b, err := strconv.ParseBool(v); err == nil {
470+
field.SetBool(b)
471+
} else {
472+
return fmt.Errorf("cannot convert string %q to bool", v)
473+
}
474+
case int, int8, int16, int32, int64:
475+
field.SetBool(reflect.ValueOf(v).Int() != 0)
476+
case uint, uint8, uint16, uint32, uint64:
477+
field.SetBool(reflect.ValueOf(v).Uint() != 0)
478+
case float32, float64:
479+
field.SetBool(reflect.ValueOf(v).Float() != 0)
480+
default:
481+
return fmt.Errorf("cannot convert %T to bool", v)
482+
}
483+
return nil
484+
}
485+
486+
// setSliceValue converts slice values
487+
func setSliceValue(field reflect.Value, value interface{}) error {
488+
sourceSlice := reflect.ValueOf(value)
489+
if sourceSlice.Kind() != reflect.Slice {
490+
return fmt.Errorf("source is not a slice")
491+
}
492+
493+
elementType := field.Type().Elem()
494+
newSlice := reflect.MakeSlice(field.Type(), sourceSlice.Len(), sourceSlice.Len())
495+
496+
for i := 0; i < sourceSlice.Len(); i++ {
497+
sourceElem := sourceSlice.Index(i)
498+
targetElem := newSlice.Index(i)
499+
500+
if elementType.Kind() == reflect.Ptr {
501+
newElem := reflect.New(elementType.Elem())
502+
if err := setFlexibleValue(newElem.Elem(), sourceElem.Interface()); err != nil {
503+
return err
504+
}
505+
targetElem.Set(newElem)
506+
} else {
507+
if err := setFlexibleValue(targetElem, sourceElem.Interface()); err != nil {
508+
return err
509+
}
510+
}
511+
}
512+
513+
field.Set(newSlice)
514+
return nil
515+
}
516+
517+
// setStructValue converts struct values
518+
func setStructValue(field reflect.Value, value interface{}) error {
519+
if mapValue, ok := value.(map[string]interface{}); ok {
520+
return flexibleMapToStruct(mapValue, field.Addr().Interface())
521+
}
522+
return fmt.Errorf("cannot convert %T to struct", value)
523+
}
524+
240525
// validateConfig validates the client configuration
241526
func validateConfig(config *Config) error {
242527
if config.BaseURL == "" {

utils_export.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@ package iyzipay
44
// This function is exported for use in examples and external verification
55
func CalculateHMACSignature(params []string, secretKey string) string {
66
return calculateHMACSignature(params, secretKey)
7+
}
8+
9+
// FlexibleJSONUnmarshal performs flexible JSON unmarshal that handles type mismatches
10+
// This function is exported for use when manually handling JSON responses
11+
func FlexibleJSONUnmarshal(data []byte, v interface{}) error {
12+
return FlexibleUnmarshal(data, v)
713
}

0 commit comments

Comments
 (0)