diff --git a/geojson/feature.go b/geojson/feature.go index 68b1f8f..0dd246d 100644 --- a/geojson/feature.go +++ b/geojson/feature.go @@ -1,6 +1,7 @@ package geojson import ( + "bytes" "fmt" "github.com/paulmach/orb" @@ -79,6 +80,11 @@ func UnmarshalFeature(data []byte) (*Feature, error) { // UnmarshalJSON handles the correct unmarshalling of the data // into the orb.Geometry types. func (f *Feature) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, []byte(`null`)) { + *f = Feature{} + return nil + } + doc := &featureDoc{} err := unmarshalJSON(data, &doc) if err != nil { diff --git a/geojson/feature_collection.go b/geojson/feature_collection.go index 0449f35..0235bc5 100644 --- a/geojson/feature_collection.go +++ b/geojson/feature_collection.go @@ -7,6 +7,7 @@ json.Unmarshaler interfaces as well as helper functions such as package geojson import ( + "bytes" "fmt" "go.mongodb.org/mongo-driver/bson" @@ -85,6 +86,11 @@ func newFeatureCollectionDoc(fc FeatureCollection) map[string]interface{} { // UnmarshalJSON decodes the data into a GeoJSON feature collection. // Extra/foreign members will be put into the `ExtraMembers` attribute. func (fc *FeatureCollection) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, []byte(`null`)) { + *fc = FeatureCollection{} + return nil + } + tmp := make(map[string]nocopyRawMessage, 4) err := unmarshalJSON(data, &tmp) diff --git a/geojson/feature_collection_test.go b/geojson/feature_collection_test.go index 2fdec65..4615b16 100644 --- a/geojson/feature_collection_test.go +++ b/geojson/feature_collection_test.go @@ -185,6 +185,40 @@ func TestFeatureCollectionMarshalJSON(t *testing.T) { } } +func TestFeatureCollectionMarshalJSON_null(t *testing.T) { + t.Run("pointer", func(t *testing.T) { + type S struct { + GeoJSON *FeatureCollection `json:"geojson"` + } + + var s S + err := json.Unmarshal([]byte(`{"geojson": null}`), &s) + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + if s.GeoJSON != nil { + t.Errorf("should be nil, got: %v", s) + } + }) + + t.Run("non-pointer", func(t *testing.T) { + type S struct { + GeoJSON FeatureCollection `json:"geojson"` + } + + var s S + err := json.Unmarshal([]byte(`{"geojson": null}`), &s) + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + if !reflect.DeepEqual(s.GeoJSON, FeatureCollection{}) { + t.Errorf("should be empty, got: %v", s) + } + }) +} + func TestFeatureCollectionMarshal(t *testing.T) { fc := NewFeatureCollection() fc.Features = nil diff --git a/geojson/feature_test.go b/geojson/feature_test.go index 717202b..a85f76b 100644 --- a/geojson/feature_test.go +++ b/geojson/feature_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "io/ioutil" + "reflect" "strings" "testing" @@ -201,6 +202,40 @@ func TestUnmarshalFeature_missingGeometry(t *testing.T) { }) } +func TestFeatureMarshalJSON_null(t *testing.T) { + t.Run("pointer", func(t *testing.T) { + type S struct { + GeoJSON *Feature `json:"geojson"` + } + + var s S + err := json.Unmarshal([]byte(`{"geojson": null}`), &s) + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + if s.GeoJSON != nil { + t.Errorf("should be nil, got: %v", s) + } + }) + + t.Run("non-pointer", func(t *testing.T) { + type S struct { + GeoJSON Feature `json:"geojson"` + } + + var s S + err := json.Unmarshal([]byte(`{"geojson": null}`), &s) + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + if !reflect.DeepEqual(s.GeoJSON, Feature{}) { + t.Errorf("should be empty, got: %v", s) + } + }) +} + func TestUnmarshalBSON_missingGeometry(t *testing.T) { t.Run("missing geometry", func(t *testing.T) { f := NewFeature(nil) diff --git a/geojson/geometry_test.go b/geojson/geometry_test.go index 6c6cd23..1edd41e 100644 --- a/geojson/geometry_test.go +++ b/geojson/geometry_test.go @@ -237,6 +237,40 @@ func TestGeometryUnmarshal_errors(t *testing.T) { } } +func TestGeometryMarshalJSON_null(t *testing.T) { + t.Run("pointer", func(t *testing.T) { + type S struct { + GeoJSON *Geometry `json:"geojson"` + } + + var s S + err := json.Unmarshal([]byte(`{"geojson": null}`), &s) + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + if s.GeoJSON != nil { + t.Errorf("should be nil, got: %v", s) + } + }) + + t.Run("feature with null geometry", func(t *testing.T) { + type S struct { + GeoJSON *Feature `json:"geojson"` + } + + var s S + err := json.Unmarshal([]byte(`{"geojson": {"type":"Feature","geometry":null,"properties":null}}`), &s) + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + if s.GeoJSON.Geometry != nil { + t.Errorf("should be nil, got: %v", s) + } + }) +} + func TestHelperTypes(t *testing.T) { // This test makes sure the marshal-unmarshal loop does the same thing. // The code and types here are complicated to avoid duplicate code.