Last active
July 8, 2024 14:35
-
-
Save alexkappa/4b541a712dc06c047a38b005178978b5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=== 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 |
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?
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
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:
And then have some magic which can look into these tags and instantiate?