Search code examples
pointersgointerfaceunmarshallingreflect

Generic unmarshalling in Go


I'm trying to figure out if there is a way to unmarshal JSON strings to a specific struct, using only the string and the intended type. Here is what I have come up with so far.

Code

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type Person struct {
    Name string `json:"name"`
}

func genericUnmarshal(jsonString string, t reflect.Type) interface{} {
    p := reflect.New(t)
    result := p.Interface()
    json.Unmarshal([]byte(jsonString), &result)
    return result
}

func main() {
    jsonData := "{\"name\":\"John\"}"
    unmarshalledPerson := genericUnmarshal(jsonData, reflect.TypeOf(Person{}))

    person := Person{Name: "John"}
    fmt.Printf("struct value: %+v type: %+v\n", person, reflect.TypeOf(person))
    fmt.Printf("unmarshalled value: %+v type: %+v\n", unmarshalledPerson, reflect.TypeOf(unmarshalledPerson))
    fmt.Printf("are variables equal: %v\n", reflect.DeepEqual(unmarshalledPerson, person))
}

Returns

struct value: {Name:John} type: main.Person
unmarshalled value: &{Name:John} type: *main.Person
are variables equal: false

The method genericUnmarshal returns a pointer to the type.

My question: Is there a way change the unmarshalled value into a struct (i.e. Person) instead of a pointer, so that reflect.DeepEqual(unmarshalledPerson, person) returns true?


Solution

  • You may compare person pointers, because reflect.DeepEqual() also accepts if the pointed values are (deeply) equal:

    Pointer values are deeply equal if they are equal using Go's == operator or if they point to deeply equal values.

    So simply do:

    fmt.Printf("are variables equal: %v\n",
        reflect.DeepEqual(unmarshalledPerson, &person))
    

    Or dereference the *Person pointer wrapped inside unmarshalledPerson so you get a Person struct:

    fmt.Printf("are variables equal: %v\n",
        reflect.DeepEqual(*unmarshalledPerson.(*Person), person))
    

    Both prints true (try it on the Go Playground):

    are variables equal: true
    are variables equal: true
    

    Also note that for your "simple" struct you may use simple == comparison:

    *unmarshalledPerson.(*Person) == person
    

    This won't be the case if you add other fields such as pointers, structs, maps etc.