Skip to content

Instantly share code, notes, and snippets.

@alexkappa
Last active Mar 14, 2022
Embed
What would you like to do?
type FleetVehicle interface {
MakeAndModel() string
}
type Vehicle struct {
Type string `json:"type"`
Make string `json:"make"`
Model string `json:"model"`
}
func (v Vehicle) MakeAndModel() string {
return fmt.Sprintf("%s %s", v.Make, v.Model)
}
type Car struct {
Vehicle
SeatingCapacity int `json:"seatingCapacity"`
TopSpeed float64 `json:"topSpeed"`
}
type Truck struct {
Vehicle
PayloadCapacity float64 `json:"payloadCapacity"`
}
type Bike struct {
Vehicle
TopSpeed float64 `json:"topSpeed"`
}
type Fleet struct {
Vehicles []FleetVehicle `json:"-"`
RawVehicles []json.RawMessage `json:"vehicles"`
}
func (f *Fleet) MarshalJSON() ([]byte, error) {
type fleet Fleet
if f.Vehicles != nil {
for _, v := range f.Vehicles {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
f.RawVehicles = append(f.RawVehicles, b)
}
}
return json.Marshal((*fleet)(f))
}
func (f *Fleet) UnmarshalJSON(b []byte) error {
type fleet Fleet
err := json.Unmarshal(b, (*fleet)(f))
if err != nil {
return err
}
for _, raw := range f.RawVehicles {
var v Vehicle
err = json.Unmarshal(raw, &v)
if err != nil {
return err
}
var i FleetVehicle
switch v.Type {
case "car":
i = &Car{}
case "truck":
i = &Truck{}
case "bike":
i = &Bike{}
default:
return errors.New("unknown vehicle type")
}
err = json.Unmarshal(raw, i)
if err != nil {
return err
}
f.Vehicles = append(f.Vehicles, i)
}
return nil
}
import (
"encoding/json"
"testing"
)
func TestJSONMarshal(t *testing.T) {
fleet := &Fleet{
Vehicles: []FleetVehicle{
&Car{Vehicle{"car", "Mercedes-Benz", "AMG C 63"}, 4, 250},
&Bike{Vehicle{"bike", "Triumph", "Bonneville T120"}, 193.12},
},
}
b, err := json.MarshalIndent(&fleet, "", " ")
if err != nil {
t.Fatal(err)
}
t.Logf("%s\n", b)
}
func TestJSONUnmarshal(t *testing.T) {
const body = `{
"vehicles": [
{"type": "car", "make": "BMW", "model": "M3", "seatingCapacity": 4, "topSpeed": 250},
{"type": "truck", "make": "Volvo", "model": "FH", "payloadCapacity": 40000},
{"type": "bike", "make": "Yamaha", "model": "YZF-R1", "topSpeed": 293}
]
}`
var f *Fleet
err := json.Unmarshal([]byte(body), &f)
if err != nil {
t.Fatal(err)
}
for _, vehicle := range f.Vehicles {
if _, ok := vehicle.(FleetVehicle); !ok {
t.Fatal("expected type to implement FleetVehicle")
}
switch v := vehicle.(type) {
case *Car:
t.Logf("%s has a seating capacity of %d and a top speed of %.2f km/h\n",
v.MakeAndModel(),
v.SeatingCapacity,
v.TopSpeed)
case *Truck:
t.Logf("%s has a payload capacity of %.2f kg\n",
v.MakeAndModel(),
v.PayloadCapacity)
case *Bike:
t.Logf("%s has a top speed of %.2f km/h\n",
v.MakeAndModel(),
v.TopSpeed)
}
}
}
=== RUN TestJSONMarshal
--- PASS: TestJSONMarshal (0.00s)
json_test.go:22: {
"vehicles": [
{
"type": "car",
"make": "Mercedes-Benz",
"model": "AMG C 63",
"seatingCapacity": 4,
"topSpeed": 250
},
{
"type": "bike",
"make": "Triumph",
"model": "Bonneville T120",
"topSpeed": 193.12
}
]
}
=== RUN TestJSONUnmarshal
--- PASS: TestJSONUnmarshal (0.00s)
json_test.go:45: BMW M3 has a seating capacity of 4 and a top speed of 250.00 km/h
json_test.go:50: Volvo FH has a payload capacity of 40000.00 kg
json_test.go:54: Yamaha YZF-R1 has a top speed of 293.00 km/h
PASS
@inikolaev
Copy link

inikolaev commented Mar 5, 2022

I'm new to Go, but is there any way to make this deserialisation logic reusable? E.g. could we add some custom tags to the vehicles to tell the name of discriminator column and subtype mapping?

Something like:

type Fleet struct {
    Vehicles []FleetVehicle `json:"vehicles" discriminator:"type,car=Car,truck=Truck,bike=Bike"`
}

And then have some magic which can look into these tags and instantiate?

@alexkappa
Copy link
Author

alexkappa commented Mar 14, 2022

Hi @inikolaev, as far as I'm aware there is no such functionality in encoding/json. My attempt was intended at replicating this functionality, but of course, it requires additional effort. One of the third-party JSON packages perhaps?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment