Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

How to unmarshal object to specific struct based on value in that object? #293

Open
DonDebonair opened this issue Jun 24, 2022 · 2 comments

Comments

@DonDebonair
Copy link

In the Buy Why?! section in the README is says:

For example, consider this JSON:

{
  "type": "person",
  "name": "Mitchell"
}

Perhaps we can't populate a specific structure without first reading the "type" field from the JSON. We could always do two passes over the decoding of the JSON (reading the "type" first, and the rest later).
However, it is much simpler to just decode this into a map[string]interface{} structure, read the "type" key, then use something like this library to decode it into the proper structure.

How would you go about this, if the JSON looks like this?:

{
  "animals": [
    {
      "type": "cat",
      "name": "Ginny",
      "purrFactor": 3
    },
    {
      "type": "dog",
      "name": "Scooby",
      "boopNose": true
    }
  ]
}

In this case, each of the items in the animals array would adhere to the same interface (for example something that implements Name()) but each different type would correspond to a different struct type that implements said interface.

The README suggests that this is what mapstructure is practically made for, but I don't understand how to implement this.

@gtors
Copy link

gtors commented Nov 22, 2022

I also don't figure out how to implement deserialization based on the discriminator field. @DonDebonair have you found a solution?

@DonDebonair
Copy link
Author

Yeah, I did. I'm using YAML, not JSON. But the principles are the same. This is roughly how I approached it:

  1. Load YAML file
  2. Unmarshal YAML as a struct with a slice of maps in it:
type Animals struct {
	Animals []map[string]any `yaml:"animals"`
}

func myFunc() {
	animalsConfig := &Animals{}
	err = yaml.Unmarshal(b, animals)
}
  1. Loop over the the slice of animals and based on the type, use mapstruct to unmarshal into actual structs
func myFunc() {
...
for _, animal := range animalsConfig.Animals {
		var animalThing Animal // Animal interface is defined somewhere
		if animal["type] == "dog" {
			animalThing = Dog{}
		}
		if animal["type"] == "cat" {
			animalThing = Cat{}
		}
		// Etc., you should probably use a switch/case here? ^
		err = mapstructure.Decode(deployment, animalThing)
		if err != nil {
			return nil, err
		}
		animals = append(animals, animalThing)
	}
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants