Sunday, September 8, 2024
HomeGolangMaintainable strategy to Unmarshal/Marshal Slice of Dynamic Varieties - Code Overview

Maintainable strategy to Unmarshal/Marshal Slice of Dynamic Varieties – Code Overview


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!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments