Last active
April 22, 2020 09:44
-
-
Save nekketsuuu/21cc9b47ef0dbff168a7ccfe4607f6b7 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
{ | |
"items": [ | |
{"type": "A", "foo": "bar"}, | |
{"type": "B", "hoge": "fuga"} | |
] | |
} |
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
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 できそう? |
そういえば required / non-required に対応するためにデータをポインタで保持するか現物で保持するか考えないといけないのすっかり忘れていました。本当はちゃんと考えるべき。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://pkg.go.dev/encoding/json?tab=doc#Unmarshaler
そういえばこれに対応してなかったけど、真面目に比較して早期 return するようにした方が良いんだろうか。