-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Correct handling of a single element batch geocoding response
- Loading branch information
1 parent
50c9d96
commit f2109a1
Showing
6 changed files
with
394 additions
and
6 deletions.
There are no files selected for viewing
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
97 changes: 97 additions & 0 deletions
97
...ing/src/main/java/com/mapbox/api/geocoding/v5/SingleElementSafeCollectionTypeAdapter.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package com.mapbox.api.geocoding.v5; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.TypeAdapterFactory; | ||
import com.google.gson.internal.$Gson$Types; | ||
import com.google.gson.reflect.TypeToken; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonToken; | ||
import com.google.gson.stream.JsonWriter; | ||
import com.google.gson.stream.MalformedJsonException; | ||
|
||
import java.io.IOException; | ||
import java.lang.reflect.Type; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
/** | ||
* Similar to {@link com.google.gson.internal.bind.CollectionTypeAdapterFactory}, | ||
* safely adapts single element collection represented as Json object or primitive. | ||
* | ||
* @param <E> collection element type | ||
* | ||
* @since 5.4.0 | ||
*/ | ||
class SingleElementSafeCollectionTypeAdapter<E> extends TypeAdapter<Collection<E>> { | ||
|
||
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { | ||
|
||
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) { | ||
final Class<? super T> rawType = typeToken.getRawType(); | ||
if (!Collection.class.isAssignableFrom(rawType)) { | ||
return null; | ||
} | ||
|
||
final Type elementType = $Gson$Types.getCollectionElementType(typeToken.getType(), rawType); | ||
final TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType)); | ||
|
||
@SuppressWarnings("unchecked") | ||
final TypeAdapter<T> adapter = | ||
(TypeAdapter<T>) new SingleElementSafeCollectionTypeAdapter<>(elementTypeAdapter); | ||
return adapter; | ||
} | ||
}; | ||
|
||
private final TypeAdapter<E> elementTypeAdapter; | ||
|
||
private SingleElementSafeCollectionTypeAdapter(final TypeAdapter<E> elementTypeAdapter) { | ||
this.elementTypeAdapter = elementTypeAdapter; | ||
} | ||
|
||
@Override | ||
public Collection<E> read(final JsonReader in) throws IOException { | ||
final JsonToken token = in.peek(); | ||
switch (token) { | ||
case BEGIN_ARRAY: | ||
final List<E> elements = new ArrayList<>(); | ||
in.beginArray(); | ||
while (in.hasNext()) { | ||
elements.add(elementTypeAdapter.read(in)); | ||
} | ||
in.endArray(); | ||
return elements; | ||
case BEGIN_OBJECT: | ||
case STRING: | ||
case NUMBER: | ||
case BOOLEAN: | ||
return Collections.singletonList(elementTypeAdapter.read(in)); | ||
case NULL: | ||
in.nextNull(); | ||
return null; | ||
case NAME: | ||
case END_ARRAY: | ||
case END_OBJECT: | ||
case END_DOCUMENT: | ||
throw new MalformedJsonException("Unexpected token: " + token); | ||
default: | ||
throw new IllegalStateException("Unprocessed token: " + token); | ||
} | ||
} | ||
|
||
@Override | ||
public void write(final JsonWriter out, final Collection<E> collection) throws IOException { | ||
if (collection == null) { | ||
out.nullValue(); | ||
return; | ||
} | ||
|
||
out.beginArray(); | ||
for (E element : collection) { | ||
elementTypeAdapter.write(out, element); | ||
} | ||
out.endArray(); | ||
} | ||
} |
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
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
186 changes: 186 additions & 0 deletions
186
...src/test/java/com/mapbox/api/geocoding/v5/SingleElementSafeCollectionTypeAdapterTest.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package com.mapbox.api.geocoding.v5; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
import com.google.gson.annotations.SerializedName; | ||
import com.google.gson.reflect.TypeToken; | ||
|
||
import org.junit.Test; | ||
|
||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertNull; | ||
|
||
public class SingleElementSafeCollectionTypeAdapterTest { | ||
|
||
@Test | ||
public void parseEmptyJson() { | ||
final Gson gson = createGson(); | ||
assertNull(gson.fromJson("", List.class)); | ||
} | ||
|
||
@Test | ||
public void parseStringArrayWithMultipleElements() { | ||
final Gson gson = createGson(); | ||
final List parsed = gson.fromJson("[\"a\", \"b\", \"c\"]", List.class); | ||
assertEquals(Arrays.asList("a", "b", "c"), parsed); | ||
} | ||
|
||
@Test | ||
public void parseStringArrayWithSingleElement() { | ||
final Gson gson = createGson(); | ||
final List parsed = gson.fromJson("\"a\"", List.class); | ||
assertEquals(Collections.singletonList("a"), parsed); | ||
} | ||
|
||
@Test | ||
public void parseNullJson() { | ||
final Gson gson = createGson(); | ||
assertNull(gson.fromJson("null", List.class)); | ||
} | ||
|
||
@Test | ||
public void parseArrayOfNulls() { | ||
final Gson gson = createGson(); | ||
final List parsed = gson.fromJson("[null, null, null]", List.class); | ||
assertEquals(Arrays.asList(null, null, null), parsed); | ||
} | ||
|
||
@Test | ||
public void parseBooleanArrayWithMultipleElements() { | ||
final Gson gson = createGson(); | ||
final List parsed = gson.fromJson("[true, false]", List.class); | ||
assertEquals(Arrays.asList(true, false), parsed); | ||
} | ||
|
||
@Test | ||
public void parseBooleanArrayWithSingleElement() { | ||
final Gson gson = createGson(); | ||
final List parsed = gson.fromJson("true", List.class); | ||
assertEquals(Collections.singletonList(true), parsed); | ||
} | ||
|
||
@Test | ||
public void parseNumberArrayWithMultipleElements() { | ||
final Gson gson = createGson(); | ||
final List parsed = gson.fromJson("[1, 2, 3]", List.class); | ||
assertEquals(Arrays.asList(1.0, 2.0, 3.0), parsed); | ||
} | ||
|
||
@Test | ||
public void parseNumberArrayWithSingleElement() { | ||
final Gson gson = createGson(); | ||
final List parsed = gson.fromJson("5", List.class); | ||
assertEquals(Collections.singletonList(5.0), parsed); | ||
} | ||
|
||
@Test | ||
public void parseCustomTypeArrayWithMultipleElements() { | ||
final Gson gson = createGson(); | ||
|
||
final String inputJson = "[" + | ||
"{\"string_field\":\"abc\",\"boolean_field\":true,\"int_field\":1}," + | ||
"{\"string_field\":\"def\",\"boolean_field\":false,\"int_field\":11}" + | ||
"]"; | ||
|
||
final TypeToken typeToken = TypeToken.getParameterized(List.class, TestType.class); | ||
final List parsed = gson.fromJson(inputJson, typeToken.getType()); | ||
|
||
final List<TestType> expectedList = Arrays.asList( | ||
new TestType("abc", true, 1), | ||
new TestType("def", false, 11) | ||
); | ||
assertEquals(expectedList, parsed); | ||
} | ||
|
||
@Test | ||
public void parseCustomTypeArrayWithSingleElement() { | ||
final Gson gson = createGson(); | ||
|
||
final String inputJson = "{\"string_field\":\"abc\",\"boolean_field\":true,\"int_field\":1}"; | ||
|
||
final TypeToken typeToken = TypeToken.getParameterized(List.class, TestType.class); | ||
final List parsed = gson.fromJson(inputJson, typeToken.getType()); | ||
|
||
final List<TestType> expectedList = | ||
Collections.singletonList(new TestType("abc", true, 1)); | ||
assertEquals(expectedList, parsed); | ||
} | ||
|
||
@Test | ||
public void serializeCustomTypeArrayWithMultipleElements() { | ||
final Gson gson = createGson(); | ||
|
||
final List<TestType> testData = Arrays.asList( | ||
new TestType("abc", true, 1), | ||
new TestType("def", false, 11) | ||
); | ||
|
||
final String serialized = gson.toJson(testData); | ||
|
||
final String expectedJson = "[" + | ||
"{\"string_field\":\"abc\",\"boolean_field\":true,\"int_field\":1}," + | ||
"{\"string_field\":\"def\",\"boolean_field\":false,\"int_field\":11}" + | ||
"]"; | ||
|
||
assertEquals(expectedJson, serialized); | ||
} | ||
|
||
@Test | ||
public void serializeCustomTypeArrayWithSingleElement() { | ||
final Gson gson = createGson(); | ||
|
||
final TestType testData = new TestType("abc", true, 1); | ||
final String serialized = gson.toJson(testData); | ||
|
||
final String expectedJson = "{\"string_field\":\"abc\",\"boolean_field\":true,\"int_field\":1}"; | ||
assertEquals(expectedJson, serialized); | ||
} | ||
|
||
private static Gson createGson() { | ||
return new GsonBuilder() | ||
.registerTypeAdapterFactory(SingleElementSafeCollectionTypeAdapter.FACTORY) | ||
.create(); | ||
} | ||
|
||
private static class TestType { | ||
|
||
@SerializedName("string_field") | ||
private final String stringField; | ||
|
||
@SerializedName("boolean_field") | ||
private final boolean booleanField; | ||
|
||
@SerializedName("int_field") | ||
private final int intField; | ||
|
||
TestType(String stringField, boolean booleanField, int intField) { | ||
this.stringField = stringField; | ||
this.booleanField = booleanField; | ||
this.intField = intField; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
|
||
TestType testType = (TestType) o; | ||
|
||
if (booleanField != testType.booleanField) return false; | ||
if (intField != testType.intField) return false; | ||
return stringField != null ? stringField.equals(testType.stringField) : testType.stringField == null; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int result = stringField != null ? stringField.hashCode() : 0; | ||
result = 31 * result + (booleanField ? 1 : 0); | ||
result = 31 * result + intField; | ||
return result; | ||
} | ||
} | ||
} |
Oops, something went wrong.