jsongoif-statementunmarshallingtype-definition

How to declare the Type of a variable dynamically in GO, using if-else-like conditions?


Given two types

type A struct {
    ID         string
    Content    []int
}

and

type B struct {
    ID         string
    Content    map[string][]int
}

and I need a function to tell me which type to use hereafter according to conditions (the purpose is to unmarshal a json string properly). I want a function like

func assign_typed_data(header string) interface{} {
    switch header {
    case "A" :
         d := new(A)
         fmt.Printf('inner type is %T \n', *d)      // inner type is A
         return *d
    case "B" :
         d := new(B)
         fmt.Printf('inner type is %T \n', *d)      // inner type is B
         return *d
    default:
    }
}

and in the outer code I can call it and unmarshal the json like follows, but the returned value turns to "map[string]interface{}".

header := "A"
data := assign_typed_data(header)
fmt.Printf('outter type is %T \n', data)      // outter type is map[string]interface{}
json.Unmarshal(json_data, &data)

I also tried simple if-else statements in the outter code directly without calling a function, like follows, but failed as well because of defined scope is local.

if header == "A" {
    data := *new(A)
}else if header == "B" {
    data := *new(B)
}
json.Unmarshal(json_data, &data)

Is there a possible way to achieve this goal in GO?


Solution

  • You have to pass a pointer to the expected data type to json.Unmarshal(). That is, *A or *B.

    Yet, assign_typed_data() returns interface{} and you take its address, so you'll be passing *interface{}.

    Change assign_typed_data() to return the pointer value *A or *B, and pass data as-is to json.Unmarshal() as it will already hold a pointer value:

    func createValue(header string) interface{} {
        switch header {
        case "A":
            d := new(A)
            fmt.Printf("inner type is %T \n", d) // inner type is *A
            return d
        case "B":
            d := new(B)
            fmt.Printf("inner type is %T \n", d) // inner type is *B
            return d
        default:
            return nil
        }
    }
    

    Testing it:

    s := `{"ID":"abc","Content":[1,2,3]}`
    data := createValue("A")
    if err := json.Unmarshal([]byte(s), data); err != nil {
        panic(err)
    }
    fmt.Printf("outer type is %T \n", data)
    fmt.Printf("outer value is %+v \n", data)
    
    s = `{"ID":"abc","Content":{"one":[1,2], "two":[3,4]}}`
    data = createValue("B")
    if err := json.Unmarshal([]byte(s), data); err != nil {
        panic(err)
    }
    fmt.Printf("outer type is %T \n", data)
    fmt.Printf("outer value is %+v \n", data)
    

    Which outputs (try it on the Go Playground):

    inner type is *main.A 
    outer type is *main.A 
    outer value is &{ID:abc Content:[1 2 3]} 
    inner type is *main.B 
    outer type is *main.B 
    outer value is &{ID:abc Content:map[one:[1 2] two:[3 4]]} 
    

    Please check related / possible duplicates which detail the issue further:

    Golang interface{} type misunderstanding

    Is it possible to dynamically set the output of json.Unmarshal?

    How to tell json.Unmarshal to use struct instead of interface