Skip to content

Instantly share code, notes, and snippets.

@nekketsuuu
Last active April 22, 2020 09:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nekketsuuu/21cc9b47ef0dbff168a7ccfe4607f6b7 to your computer and use it in GitHub Desktop.
Save nekketsuuu/21cc9b47ef0dbff168a7ccfe4607f6b7 to your computer and use it in GitHub Desktop.
{
"items": [
{"type": "A", "foo": "bar"},
{"type": "B", "hoge": "fuga"}
]
}
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
)
type Object struct {
Items []Item `json:"items"`
}
type Item struct {
Type string
Content interface{}
}
type ItemA struct {
Foo string `json:"foo"`
}
type ItemB struct {
Hoge string `json:"hoge"`
}
func (item *Item) UnmarshalJSON(data []byte) error {
ty := struct {
Type string `json:"type"`
}{}
if err := json.Unmarshal(data, &ty); err != nil {
return err
}
item.Type = ty.Type
switch ty.Type {
case "A":
var itemA ItemA
if err := json.Unmarshal(data, &itemA); err != nil {
return fmt.Errorf("Invalid as ItemA: %w", err)
}
item.Content = itemA
case "B":
var itemB ItemB
if err := json.Unmarshal(data, &itemB); err != nil {
return fmt.Errorf("Invalid as ItemB: %w", err)
}
item.Content = itemB
default:
return errors.New("Unknown type: " + ty.Type)
}
return nil
}
func main() {
body, err := ioutil.ReadFile("input.json")
if err != nil {
log.Fatal(err)
}
var obj Object
err = json.Unmarshal(body, &obj)
if err != nil {
log.Fatal(err)
}
for _, item := range obj.Items {
switch item.Type {
case "A":
itemA, _ := item.Content.(ItemA)
fmt.Printf("ItemA %s\n", itemA.Foo)
case "B":
itemB, _ := item.Content.(ItemB)
fmt.Printf("ItemB %s\n", itemB.Hoge)
default:
panic("Unknown item")
}
}
}
/*
* この例では Item を tag と interface{} の組として表現しています(タグ付きユニオン型の内部表現みたいな感じです)。
* 他の表現として、ひとつだけ non-nil なポインターの列として定義することもできます。
* そうすると型アサーションが不要になりますが nil check が必要になります。また、ひとつだけ nil という保証を人間が頑張って守らないといけません。
* このためこの表現は採用しませんでした。
*
* ```go
* type Item struct {
* *ItemA
* *ItemB
* }
* ```
*/
// TODO(nekketsuuu): これくらいなら go generate できそう?
@nekketsuuu
Copy link
Author

https://pkg.go.dev/encoding/json?tab=doc#Unmarshaler

By convention, to approximate the behavior of Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.

そういえばこれに対応してなかったけど、真面目に比較して早期 return するようにした方が良いんだろうか。

@nekketsuuu
Copy link
Author

そういえば required / non-required に対応するためにデータをポインタで保持するか現物で保持するか考えないといけないのすっかり忘れていました。本当はちゃんと考えるべき。

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