Reflections in Go, for Cats

Posted on by in Development, golang

Several months ago, I had a story requiring metaprogramming in golang. I wasn’t very familiar with reflections in Go, and the available docs and write-ups aren’t the best for me, since I’m a learn-by-example kind of person. Having come to Go (ha) as a Rubyist, the lack of generics left a little bit of a hole in my heart, but a stiff upper lip, I pressed on and learned about reflections. Since I had to write my own examples anyway, it made sense to clean this up and turn it into a blog.

My simplified use case

  1. We want to be able to deserialize and validate registered types from []byte with reflect.Type as parameters, but only if previously registered.
  2. Our interface Pet uses the Adopt([]byte)function for deserialization. We’ll need to initialize the incoming type and call Adopt, passing in the data.
  3. Last, consumers of our module should be able to register a reflect.Type and a validator (Veterinarian.Examine in the code example) for this type, with a Registrar. The type is expected to implement the Pet interface.

The Pet interface:

Pet is a domesticated animal companion for humans. It can be “adopted” via Adopt.

type Pet interface {
    Adopt([]byte) error
    IsVaccinated() bool
    IsHealthy() bool
    PetName() string
    Type() string
    Vaccinate()
}

Cat implements Pet:

type Cat struct {
	Name string `json:"name"`
	Healthy bool
	Vaccinated bool `json:vaccinated`
}
    func (f *Cat) Adopt(data []byte) error { return json.Unmarshal(data, f) }
    func (f *Cat) IsVaccinated() bool { return f.Vaccinated } 
    func (f *Cat) IsHealthy() bool { return f.Healthy }
    func (f *Cat) Adopt(data []byte) error { return json.Unmarshal(data, f) }
    func (f *Cat) PetName() string { return f.Name }
    func (f *Cat) Sound() string { return "meow!" }
    func (f *Cat) Type() string { return "Felis Catus" }
    func (f *Cat) Vaccinate() { f.Vaccinated = true }

We can ensure Cat implements the Pet interface. If it doesn’t, this doesn’t compile:

var _ Pet = &Cat{}

Our Simple Veterinarian

type Veterinarian struct {
	vtype   reflect.Type
	Examine func(Pet) bool
}

Our Simple Registrar

type Registrar struct {
	RegisteredPets map[string]Veterinarian
}

The rest of this is in the linked Gist below. I have annotated in detail the test code and implementations. If you download and run the code, particularly if you step through with a debugger, you can examine the test assertions. You can also add your own assertions to explore the answers to your own questions.

In the first set of tests, we examine the basics of reflection by playing around with initializing a Cat struct just by knowing its type, and then calling both Pet and Cat functions on it. The test assertions demonstrate the explanations.

In the next set of tests, we apply this knowledge to our deserialization task.

The final set of tests are to test the implementation of Veterinarian and Registrar. These are left for you to make pass, if you like.


Interested in more software development tips & insights? Visit the development section on our blog!