I’m questioning what is perhaps probably the most affordable strategy to stop writing a bunch of code each time I’ve to deal with JSON like the next instance. This can be a potential response from a REST API I’ve no management over. It incorporates a listing of pets the place every may very well be certainly one of an enum of 1+ varieties.
{
"pets": [
{
"type": "Cat",
"name": "Mittens",
"is_angry": true
},
{
"type": "Dog",
"name": "Spot",
"has_ball": false
}
]
}
I’ve managed to put in writing this to deal with it. Nevertheless it feels form of hacky, particularly if this can be a sample that might seem many instances when interacting with the REST API.
package deal primary
import (
"encoding/json"
"fmt"
)
sort Pet struct {
Kind string `json:"sort"`
Identify string `json:"identify"`
}
sort DynamicPet interface {
isPet()
}
func (p Pet) isPet() {}
sort Cat struct {
Pet
IsAngry bool `json:"is_angry"`
}
sort Canine struct {
Pet
HasBall bool `json:"has_ball"`
}
var petTypeMap = map[string]func() DynamicPet{
"Cat": func() DynamicPet { return &Cat{} },
"Canine": func() DynamicPet { return &Canine{} },
}
const (
CatType = "Cat"
DogType = "Canine"
PingusType = "Pingus"
)
sort DynamicPetWrapper struct {
Pet DynamicPet `json:"-"`
}
func (p *DynamicPetWrapper) UnmarshalJSON(information []byte) error {
var typeData struct {
Kind string `json:"sort"`
}
if err := json.Unmarshal(information, &typeData); err != nil {
return err
}
petType, okay := petTypeMap[typeData.Type]
if !okay {
return fmt.Errorf("unknown pet sort: %s", typeData.Kind)
}
p.Pet = petType()
if err := json.Unmarshal(information, p.Pet); err != nil {
return err
}
return nil
}
func (p DynamicPetWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(p.Pet)
}
sort PetList struct {
Pets []DynamicPetWrapper `json:"pets"`
}
func primary() {
// json instance PetList
jsonData := []byte(`
{
"pets": [
{
"type": "Cat",
"name": "Mittens",
"is_angry": true
},
{
"type": "Dog",
"name": "Spot",
"has_ball": false
}
]
}
`)
// deserialize json into PetList dynamically unmarshalling into the proper varieties
var petList PetList
if err := json.Unmarshal(jsonData, &petList); err != nil {
fmt.Println("Error:", err)
return
}
// iterate over the listing of pets and do logic primarily based on pet sort
for _, petWrapper := vary petList.Pets {
swap pet := petWrapper.Pet.(sort) {
case *Cat:
fmt.Printf("Cat: is_angry %vn", pet.IsAngry)
case *Canine:
fmt.Printf("Canine: has_ball %vn", pet.HasBall)
}
}
// serialize again to JSON to verify it labored each methods
jsonData2, err := json.Marshal(petList)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(jsonData2))
}
One factor that retains me up at evening about that is that in Rust, I don’t want a wrapper sort, or to put in writing particular marshaling logic, it simply works, like magic, with half the code. And the compiler will yell at me if I haven’t dealt with any new sort I’d implement, because of how match arms work. I can anticipate to simply use the identical varieties, with out additional layering or complexity, in all places in my code.
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
enum Pet {
Cat(Cat),
Canine(Canine),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Cat {
identify: String,
is_angry: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Canine {
identify: String,
has_ball: bool,
}
#[derive(Serialize, Deserialize, Debug)]
struct PetList {
pets: Vec<Pet>,
}
fn primary() {
// json instance PetList
let json = r#"
{
"pets": [
{
"type": "Cat",
"name": "Mittens",
"is_angry": true
},
{
"type": "Dog",
"name": "Spot",
"has_ball": false
}
]
}
"#;
// deserialize json into PetList dynamically unmarshalling into the proper varieties
let pet_list: PetList = serde_json::from_str(json).unwrap();
// can simply iterate over the listing of pets and do logic primarily based on pet sort
for pet in pet_list.pets.iter() {
match pet {
Pet::Cat(cat) => println!("Cat: is_angry {}", cat.is_angry),
Pet::Canine(canine) => println!("Canine: has_ball {}", canine.has_ball),
}
}
// serialize again to JSON to verify it labored each methods
let json = serde_json::to_string(&pet_list).unwrap();
println!("{}", json);
}
I simply really feel like my code base will spiral uncontrolled each time I’ve to work together with fancy JSON in Go. Thanks for any dialog on this matter!