-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e48250b
commit 3d09451
Showing
6 changed files
with
355 additions
and
26 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,136 @@ | ||
# JSONPoly | ||
|
||
Utilities for marshalling and unmarshalling polymorphic JSON objects in Go. | ||
Utilities for marshalling and unmarshalling polymorphic JSON objects in Go | ||
without generating code. | ||
|
||
## Install | ||
## Usage | ||
|
||
``` | ||
go get github.com/lovromazgon/jsonpoly | ||
go get github.com/lovromazgon/jsonpoly@latest | ||
``` | ||
|
||
## Quick start | ||
Say that you have an interface `Shape` and two structs `Triangle` and `Square` | ||
that implement it. The structs have a method `Kind` that returns the name of | ||
the shape. | ||
|
||
Coming soon. Meanwhile, look at the example in `container_test.go`. | ||
```go | ||
package shapes | ||
|
||
type Shape interface { | ||
Kind() string | ||
} | ||
|
||
func (Triangle) Kind() string { return "triangle" } | ||
func (Square) Kind() string { return "square" } | ||
|
||
type Square struct { | ||
TopLeft [2]int `json:"top-left"` | ||
Width int `json:"width"` | ||
} | ||
|
||
type Triangle struct { | ||
P0 [2]int `json:"p0"` | ||
P1 [2]int `json:"p1"` | ||
P2 [2]int `json:"p2"` | ||
} | ||
``` | ||
|
||
You need to define a type that implements the `jsonpoly.Helper` interface and | ||
can marshal and unmarshal the field(s) used to determine the type of the object. | ||
In this case, the field is `kind`. You also need to define a map that maps the | ||
values of the field to the types. | ||
|
||
```go | ||
var knownShapes = map[string]Shape{ | ||
Triangle{}.Kind(): Triangle{}, | ||
Square{}.Kind(): Square{}, | ||
} | ||
|
||
type ShapeJSONHelper struct { | ||
Kind string `json:"kind"` | ||
} | ||
|
||
func (h *ShapeJSONHelper) Get() Shape { | ||
return knownShapes[h.Kind] | ||
} | ||
|
||
func (h *ShapeJSONHelper) Set(s Shape) { | ||
h.Kind = s.Kind() | ||
} | ||
``` | ||
|
||
Now you can marshal and unmarshal polymorphic JSON objects using `jsonpoly.Container`. | ||
|
||
```go | ||
inputShape := Square{TopLeft: [2]int{1, 2}, Width: 4} | ||
|
||
var c jsonpoly.Container[Polytope, *PolytopeJSONHelper] | ||
c.Value = inputShape | ||
|
||
b, err := json.Marshal(c) | ||
fmt.Println(string(b)) // {"kind":"square","top-left":[1,2],"width":4} | ||
|
||
c.Value = nil // reset before unmarshalling | ||
err = json.Unmarshal(b, &c) | ||
fmt.Printf("%T\n", c.Value) // shapes.Square | ||
``` | ||
|
||
## FAQ | ||
|
||
### How is this different than [`github.com/polyfloyd/gopolyjson`](https://github.com/polyfloyd/gopolyjson)? | ||
|
||
`gopolyjson` is a great package, but it has its limitations. Here's a list of | ||
differences that can help you determine what package to use: | ||
|
||
- `gopolyjson` requires you to add a private method to your interface without | ||
parameters or return arguments. As a consequence, you have to put all types | ||
that implement the interface in the same package. `jsonpoly` does not require | ||
you to add any methods to your types. | ||
- `gopolyjson` requires you to generate code for each type you want to serialize. | ||
Since the generated code adds methods to the types, you can not generate the | ||
code for types from external packages. `jsonpoly` works without generating code. | ||
- Because `gopolyjson` uses generated code, it can be faster than `jsonpoly`. | ||
- `gopolyjson` only supports a single field at the root of the JSON to determine | ||
the type of the object, while `jsonpoly` supports multiple fields. | ||
- `gopolyjson` does not handle unknown types which can be an issue with | ||
backwards compatibility. `jsonpoly` can handle unknown types by having a | ||
"catch-all" type. | ||
|
||
### How can I handle unknown types? | ||
|
||
If you want to handle unknown types, you can define a "catch-all" type. The type | ||
should be returned by the `Get` method of the `jsonpoly.Helper` implementation | ||
whenever the type of the object is not recognized. | ||
|
||
Keep in mind that the field used to determine the type of the object should be | ||
marked with the `json:"-"` tag, as it is normally handled by the helper. Not | ||
doing so will result in duplicating the field. | ||
|
||
```go | ||
type Unknown struct { | ||
XKind string `json:"-"` | ||
json.RawMessage // Store the raw json if needed. | ||
} | ||
|
||
func (u Unknown) Kind() string { return u.XKind } | ||
|
||
type ShapeJSONHelper struct { | ||
Kind string `json:"kind"` | ||
} | ||
|
||
func (h *ShapeJSONHelper) Get() Shape { | ||
s, ok := knownShapes[h.Kind] | ||
if !ok { | ||
return Unknown{XKind: h.Kind} | ||
} | ||
return s | ||
} | ||
``` | ||
|
||
### Can I use multiple fields to determine the type of the object? | ||
|
||
Yes, you can use any number of fields to determine the type of the object. You | ||
just need to define a struct that contains all the fields and implements the | ||
`jsonpoly.Helper` interface. | ||
|
||
For more information on how to do this, check the [`example`](./example) directory. |
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
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
Oops, something went wrong.