Skip to content

Commit c6cbe31

Browse files
authored
Properties passed to Feature.fromGeometry and CarmenFeature.properties should be Nullable [#813] (#815)
1 parent b864dc4 commit c6cbe31

5 files changed

Lines changed: 108 additions & 20 deletions

File tree

services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/CarmenFeature.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.mapbox.api.geocoding.v5.GeocodingCriteria.GeocodingTypeCriteria;
1212
import com.mapbox.geojson.BoundingBox;
1313
import com.mapbox.geojson.Feature;
14+
import com.mapbox.geojson.GeoJson;
1415
import com.mapbox.geojson.Geometry;
1516
import com.mapbox.geojson.Point;
1617
import com.mapbox.geojson.gson.BoundingBoxDeserializer;
@@ -24,7 +25,7 @@
2425

2526
/**
2627
* The Features key in the geocoding API response contains the majority of information you'll want
27-
* to use. It extends the {@link Feature} object in GeoJSON and adds several additional attribute
28+
* to use. It extends the {@link GeoJson} object in GeoJSON and adds several additional attribute
2829
* which further describe the geocoding result.
2930
* <p>
3031
* A Geocoding id is a String in the form {@code {type}.{id}} where {@code {type}} is the lowest
@@ -41,7 +42,7 @@
4142
* @since 1.0.0
4243
*/
4344
@AutoValue
44-
public abstract class CarmenFeature implements Serializable {
45+
public abstract class CarmenFeature implements GeoJson, Serializable {
4546

4647
private static final String TYPE = "Feature";
4748

@@ -60,7 +61,12 @@ public static CarmenFeature fromJson(@NonNull String json) {
6061
.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer())
6162
.registerTypeAdapterFactory(GeocodingAdapterFactory.create())
6263
.create();
63-
return gson.fromJson(json, CarmenFeature.class);
64+
CarmenFeature feature = gson.fromJson(json, CarmenFeature.class);
65+
// Even thought properties are Nullable,
66+
// Feature object will be created with properties set to an empty object,
67+
return feature.properties() == null
68+
? feature.toBuilder().properties(new JsonObject()).build()
69+
: feature;
6470
}
6571

6672
/**
@@ -72,12 +78,14 @@ public static CarmenFeature fromJson(@NonNull String json) {
7278
@NonNull
7379
public static Builder builder() {
7480
return new AutoValue_CarmenFeature.Builder()
75-
.type(TYPE);
81+
.type(TYPE)
82+
.properties(new JsonObject());
7683
}
7784

7885
//
7986
// Feature specific attributes
8087
//
88+
// Note that CarmenFeature cannot extend Feature due to AutoValue limitations
8189

8290
/**
8391
* This describes the TYPE of GeoJson geometry this object is, thus this will always return
@@ -89,6 +97,7 @@ public static Builder builder() {
8997
*/
9098
@NonNull
9199
@SerializedName("type")
100+
@Override
92101
public abstract String type();
93102

94103
/**
@@ -102,6 +111,7 @@ public static Builder builder() {
102111
* @since 3.0.0
103112
*/
104113
@Nullable
114+
@Override
105115
public abstract BoundingBox bbox();
106116

107117
/**
@@ -132,7 +142,7 @@ public static Builder builder() {
132142
* @return a {@link JsonObject} which holds this features current properties
133143
* @since 1.0.0
134144
*/
135-
@NonNull
145+
@Nullable
136146
public abstract JsonObject properties();
137147

138148
//
@@ -282,14 +292,22 @@ public static TypeAdapter<CarmenFeature> typeAdapter(Gson gson) {
282292
* @return a JSON string which represents this CarmenFeature
283293
* @since 3.0.0
284294
*/
295+
@Override
285296
@SuppressWarnings("unused")
286297
public String toJson() {
287298
Gson gson = new GsonBuilder()
288299
.registerTypeAdapter(Geometry.class, new GeometryTypeAdapter())
289300
.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer())
290301
.registerTypeAdapterFactory(GeocodingAdapterFactory.create())
291302
.create();
292-
return gson.toJson(this, CarmenFeature.class);
303+
304+
// Empty properties -> should not appear in json string
305+
CarmenFeature feature = this;
306+
if (properties() != null && properties().size() == 0) {
307+
feature = toBuilder().properties(null).build();
308+
}
309+
310+
return gson.toJson(feature, CarmenFeature.class);
293311
}
294312

295313
/**
@@ -355,7 +373,7 @@ public abstract static class Builder {
355373
* @return this builder for chaining options together
356374
* @since 3.0.0
357375
*/
358-
public abstract Builder properties(@NonNull JsonObject properties);
376+
public abstract Builder properties(@Nullable JsonObject properties);
359377

360378
/**
361379
* A string representing the feature in the requested language.

services-geocoding/src/test/java/com/mapbox/api/geocoding/v5/models/CarmenFeatureTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@
77
import static org.hamcrest.Matchers.nullValue;
88
import static org.hamcrest.core.IsEqual.equalTo;
99
import static org.hamcrest.junit.MatcherAssert.assertThat;
10+
import static org.junit.Assert.assertFalse;
1011

1112
import com.google.gson.JsonObject;
1213
import com.mapbox.api.geocoding.v5.GeocodingTestUtils;
1314
import com.mapbox.api.geocoding.v5.MapboxGeocoding;
1415
import com.mapbox.core.TestUtils;
1516
import com.mapbox.geojson.CoordinateContainer;
17+
import com.mapbox.geojson.Feature;
18+
import com.mapbox.geojson.LineString;
1619
import com.mapbox.geojson.Point;
20+
21+
import org.junit.Assert;
1722
import org.junit.Test;
1823
import retrofit2.Response;
1924

2025
import java.io.IOException;
26+
import java.util.ArrayList;
27+
import java.util.List;
2128
import java.util.Locale;
2229

2330
public class CarmenFeatureTest extends GeocodingTestUtils {
@@ -149,4 +156,27 @@ public void ForwardGeocode_withValidChineseResponse() throws Exception {
149156
assertThat(feature.bbox().east(), equalTo(107.025123596));
150157
assertThat(feature.bbox().north(), equalTo(39.6012458800001));
151158
}
159+
160+
@Test
161+
public void testNullProperties() {
162+
CarmenFeature feature = CarmenFeature.builder()
163+
.geometry(Point.fromLngLat(-77, 38))
164+
.build();
165+
String jsonString = feature.toJson();
166+
assertFalse(jsonString.contains("\"properties\":{}"));
167+
168+
// Feature (empty Properties) -> Json (null Properties) -> Equavalent Feature
169+
CarmenFeature featureFromJson = CarmenFeature.fromJson(jsonString);
170+
assertEquals(featureFromJson, feature);
171+
}
172+
173+
@Test
174+
public void testNullPropertiesJson() {
175+
String jsonString = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-77.0, 38.0]}}";
176+
CarmenFeature feature = CarmenFeature.fromJson(jsonString);
177+
178+
// Json( null Properties) -> Feature (empty Properties) -> Json(null Properties)
179+
String fromFeatureJsonString = feature.toJson();
180+
assertEquals(fromFeatureJsonString, jsonString);
181+
}
152182
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"id":"address.3982178573139850","type":"Feature","place_type":["address"],"relevance":1,"properties":{},"text":"Pennsylvania Ave NW","place_name":"1600 Pennsylvania Ave NW, Washington, District of Columbia 20006, United States","center":[-77.036543,38.897702],"geometry":{"type":"Point","coordinates":[-77.036543,38.897702]},"address":"1600","context":[{"id":"neighborhood.291451","text":"Downtown"},{"id":"postcode.8031694603652840","text":"20006"},{"id":"place.11387590027246050","wikidata":"Q61","text":"Washington"},{"id":"region.3403","short_code":"US-DC","wikidata":"Q61","text":"District of Columbia"},{"id":"country.3145","short_code":"us","wikidata":"Q30","text":"United States"}]}
1+
{"id":"address.3982178573139850","type":"Feature","place_type":["address"],"relevance":1,"text":"Pennsylvania Ave NW","place_name":"1600 Pennsylvania Ave NW, Washington, District of Columbia 20006, United States","center":[-77.036543,38.897702],"geometry":{"type":"Point","coordinates":[-77.036543,38.897702]},"address":"1600","context":[{"id":"neighborhood.291451","text":"Downtown"},{"id":"postcode.8031694603652840","text":"20006"},{"id":"place.11387590027246050","wikidata":"Q61","text":"Washington"},{"id":"region.3403","short_code":"US-DC","wikidata":"Q61","text":"District of Columbia"},{"id":"country.3145","short_code":"us","wikidata":"Q30","text":"United States"}]}

services-geojson/src/main/java/com/mapbox/geojson/Feature.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,16 @@ public static Feature fromJson(@NonNull String json) {
6666
gson.registerTypeAdapter(Point.class, new PointDeserializer());
6767
gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer());
6868
gson.registerTypeAdapter(Geometry.class, new GeometryDeserializer());
69-
return gson.create().fromJson(json, Feature.class);
69+
Feature feature = gson.create().fromJson(json, Feature.class);
70+
71+
// Even thought properties are Nullable,
72+
// Feature object will be created with properties set to an empty object,
73+
// so that addProperties() would work
74+
if (feature.properties() != null) {
75+
return feature;
76+
}
77+
return new AutoValue_Feature(TYPE, feature.bbox(),
78+
feature.id(), feature.geometry(), new JsonObject());
7079
}
7180

7281
/**
@@ -105,8 +114,9 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable Boundi
105114
* method
106115
* @since 1.0.0
107116
*/
108-
public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObject properties) {
109-
return new AutoValue_Feature(TYPE, null, null, geometry, properties);
117+
public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonObject properties) {
118+
return new AutoValue_Feature(TYPE, null, null, geometry,
119+
properties == null ? new JsonObject() : properties);
110120
}
111121

112122
/**
@@ -120,9 +130,10 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj
120130
* method
121131
* @since 1.0.0
122132
*/
123-
public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObject properties,
133+
public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonObject properties,
124134
@Nullable BoundingBox bbox) {
125-
return new AutoValue_Feature(TYPE, bbox, null, geometry, properties);
135+
return new AutoValue_Feature(TYPE, bbox, null, geometry,
136+
properties == null ? new JsonObject() : properties);
126137
}
127138

128139
/**
@@ -135,9 +146,10 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj
135146
* @return {@link Feature}
136147
* @since 1.0.0
137148
*/
138-
public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObject properties,
149+
public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonObject properties,
139150
@Nullable String id) {
140-
return new AutoValue_Feature(TYPE, null, id, geometry, properties);
151+
return new AutoValue_Feature(TYPE, null, id, geometry,
152+
properties == null ? new JsonObject() : properties);
141153
}
142154

143155
/**
@@ -225,7 +237,14 @@ public String toJson() {
225237
GsonBuilder gson = new GsonBuilder();
226238
gson.registerTypeAdapter(Point.class, new PointSerializer());
227239
gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer());
228-
return gson.create().toJson(this);
240+
241+
// Empty properties -> should not appear in json string
242+
Feature feature = this;
243+
if (properties().size() == 0) {
244+
feature = new AutoValue_Feature(TYPE, bbox(), id(), geometry(), null);
245+
}
246+
247+
return gson.create().toJson(feature);
229248
}
230249

231250
/**

services-geojson/src/test/java/com/mapbox/geojson/FeatureTest.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.mapbox.geojson;
22

33
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
45
import static org.junit.Assert.assertNotNull;
56
import static org.junit.Assert.assertNull;
67
import static org.junit.Assert.assertTrue;
@@ -47,7 +48,7 @@ public void bbox_doesNotSerializeWhenNotPresent() throws Exception {
4748
Feature feature = Feature.fromGeometry(lineString);
4849
compareJson(feature.toJson(),
4950
"{\"type\":\"Feature\",\"geometry\":{\"type\":"
50-
+ "\"LineString\",\"coordinates\":[[1,2],[2,3]]},\"properties\":{}}");
51+
+ "\"LineString\",\"coordinates\":[[1,2],[2,3]]}}");
5152
}
5253

5354
@Test
@@ -76,7 +77,7 @@ public void bbox_doesSerializeWhenPresent() throws Exception {
7677
BoundingBox bbox = BoundingBox.fromLngLats(1.0, 2.0, 3.0, 4.0);
7778
Feature feature = Feature.fromGeometry(lineString, bbox);
7879
compareJson("{\"type\":\"Feature\",\"bbox\":[1.0,2.0,3.0,4.0],\"geometry\":"
79-
+ "{\"type\":\"LineString\",\"coordinates\":[[1,2],[2,3]]},\"properties\":{}}",
80+
+ "{\"type\":\"LineString\",\"coordinates\":[[1,2],[2,3]]}}",
8081
feature.toJson());
8182
}
8283

@@ -103,7 +104,12 @@ public void testNullProperties() {
103104
coordinates.add(Point.fromLngLat(4.5, 6.7));
104105
LineString line = LineString.fromLngLats(coordinates);
105106
Feature feature = Feature.fromGeometry(line);
106-
assertTrue(feature.toJson().contains("\"properties\":{}"));
107+
String jsonString = feature.toJson();
108+
assertFalse(jsonString.contains("\"properties\":{}"));
109+
110+
// Feature (empty Properties) -> Json (null Properties) -> Equavalent Feature
111+
Feature featureFromJson = Feature.fromJson(jsonString);
112+
assertEquals(featureFromJson, feature);
107113
}
108114

109115
@Test
@@ -115,6 +121,21 @@ public void testNonNullProperties() {
115121
JsonObject properties = new JsonObject();
116122
properties.addProperty("key", "value");
117123
Feature feature = Feature.fromGeometry(line, properties);
118-
assertTrue(feature.toJson().contains("\"properties\":{\"key\":\"value\"}"));
124+
String jsonString = feature.toJson();
125+
assertTrue(jsonString.contains("\"properties\":{\"key\":\"value\"}"));
126+
127+
// Feature (non-empty Properties) -> Json (non-empty Properties) -> Equavalent Feature
128+
assertEquals(Feature.fromJson(jsonString), feature);
129+
}
130+
131+
@Test
132+
public void testNullPropertiesJson() {
133+
String jsonString = "{\"type\":\"Feature\",\"bbox\":[1.0,2.0,3.0,4.0],\"geometry\":"
134+
+ "{\"type\":\"LineString\",\"coordinates\":[[1.0,2.0],[2.0,3.0]]}}";
135+
Feature feature = Feature.fromJson(jsonString);
136+
137+
// Json( null Properties) -> Feature (empty Properties) -> Json(null Properties)
138+
String fromFeatureJsonString = feature.toJson();
139+
assertEquals(fromFeatureJsonString, jsonString);
119140
}
120141
}

0 commit comments

Comments
 (0)