Skip to content

Instantly share code, notes, and snippets.

@alexkappa
Last active July 8, 2024 14:35
Show Gist options
  • Save alexkappa/4b541a712dc06c047a38b005178978b5 to your computer and use it in GitHub Desktop.
Save alexkappa/4b541a712dc06c047a38b005178978b5 to your computer and use it in GitHub Desktop.
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
@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?

@jasonm-unity
Copy link

note that strictly speaking, RawVehicles []json.RawMessage `json:"vehicles"` does not need to be part of the Fleet type.
It can be local to the custom marshalling functions.

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