diff --git a/item.go b/item.go index d45ff15..2a7fa62 100644 --- a/item.go +++ b/item.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "regexp" "github.com/go-viper/mapstructure/v2" @@ -122,6 +123,14 @@ func (item Item) MarshalJSON() ([]byte, error) { return json.Marshal(itemMap) } +var geometryUnmarshaler json.Unmarshaler + +// GeometryUnmarshaler allows a custom geometry type that satisfies the json.Unmarshaler interface to be provided. +// If not set, item geometries will be unmarshaled as a map[string]any. +func GeometryUnmarshaler(g json.Unmarshaler) { + geometryUnmarshaler = g +} + func (item *Item) UnmarshalJSON(data []byte) error { itemMap := map[string]any{} if err := json.Unmarshal(data, &itemMap); err != nil { @@ -166,6 +175,33 @@ func (item *Item) UnmarshalJSON(data []byte) error { return err } + if geometryUnmarshaler != nil { + geometryMap, ok := itemMap["geometry"] + if ok { + geometryData, err := json.Marshal(geometryMap) + if err != nil { + return err + } + + var gv any + gvt := reflect.TypeOf(geometryUnmarshaler) + if gvt.Kind() == reflect.Pointer { + gv = reflect.New(gvt.Elem()).Interface() + } else { + gv = reflect.New(gvt).Elem().Interface() + } + g, ok := gv.(json.Unmarshaler) + if !ok { + return fmt.Errorf("expected %#v to satisfy the json.Unmarshaler interface", gv) + } + if err := g.UnmarshalJSON(geometryData); err != nil { + return err + } + + item.Geometry = g + } + } + return nil } diff --git a/item_test.go b/item_test.go index 2d9e57f..8d6eb7e 100644 --- a/item_test.go +++ b/item_test.go @@ -63,3 +63,97 @@ func TestItemMarshal(t *testing.T) { assert.JSONEq(t, expected, string(data)) } + +func TestItemUnmarshal(t *testing.T) { + data := `{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "item-id", + "geometry": { + "type": "Point", + "coordinates": [1, 2] + }, + "properties": { + "test": "value" + }, + "links": [ + { + "rel": "self", + "href": "https://example.com/stac/item-id" + } + ], + "assets": { + "thumbnail": { + "title": "Thumbnail", + "href": "https://example.com/stac/item-id/thumb.png", + "type": "image/png" + } + } + }` + + item := &stac.Item{} + require.NoError(t, json.Unmarshal([]byte(data), item)) + + assert.Equal(t, "item-id", item.Id) + + require.NotNil(t, item.Geometry) + + g, ok := item.Geometry.(map[string]any) + require.True(t, ok) + geometryType, ok := g["type"].(string) + require.True(t, ok) + assert.Equal(t, "Point", geometryType) +} + +type TestGeometry struct { + data []byte +} + +func (g *TestGeometry) UnmarshalJSON(data []byte) error { + g.data = data + return nil +} + +func TestGeometryUnmarshal(t *testing.T) { + original := &TestGeometry{} + stac.GeometryUnmarshaler(original) + defer stac.GeometryUnmarshaler(nil) + + data := `{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "item-id", + "geometry": { + "type": "Point", + "coordinates": [1, 2] + }, + "properties": { + "test": "value" + }, + "links": [ + { + "rel": "self", + "href": "https://example.com/stac/item-id" + } + ], + "assets": { + "thumbnail": { + "title": "Thumbnail", + "href": "https://example.com/stac/item-id/thumb.png", + "type": "image/png" + } + } + }` + + item := &stac.Item{} + require.NoError(t, json.Unmarshal([]byte(data), item)) + + assert.Equal(t, "item-id", item.Id) + + assert.Nil(t, original.data) + require.NotNil(t, item.Geometry) + + g, ok := item.Geometry.(*TestGeometry) + require.True(t, ok) + assert.NotNil(t, g.data) +}