How to parse a json response with multi type values coming for same field? - json

How to parse answerData key from json response in kotlin as it is changing its type in each block? I tried keeping it Any but was not able to type cast. how to parse answerData?
{
"status": "OK",
"data": [
{
"id": 10,
"answerData": null
},
{
"id": 21,
"answerData": {
"selectionOptionId": 0,
"selectionOptionText": null
}
},
{
"id": 45,
"answerData": {
"IsAffiliatedWithSeller": false,
"AffiliationDescription": null
}
},
{
"id" : 131,
"answerData" : [
{ "2" : "Chapter 11" },
{ "3" : "Chapter 12" },
{ "1" : "Chapter 7" }
]
},
{
"id" : 140,
"answerData" : [
{
"liabilityTypeId" : 2,
"monthlyPayment" : 200,
"remainingMonth" : 2,
"liabilityName" : "Separate Maintenance",
"name" : "Two"
},
{
"liabilityTypeId" : 1,
"monthlyPayment" : 300,
"remainingMonth" : 1,
"liabilityName" : "Child Support",
"name" : "Three"
}
]
}
]
}

As commented and explained in other answers you really should ask changes to the JSON format. However it is not so unusual to have list of elements of which the data included varies. For such case there should at least be some field indication the type of data to be deserialized. (not saying it is not an anti-pattern sometimes it might be).
If you reach that agreement it is possible to use - for example - RuntimeTypeAdapterFactory
like explained in linked question (sorry it is Java).
If not you will run into troubles. It is still quite easy to isolate the problem. Not saying it is easy to solve. I present one possible (sorry again, Java but guess it is easily adaptable to Kotlin) solution. I have used lots of inner static classes to make the code more compact. The actual logic has not so many rows most of the code is to map your JSON into java classes.
Make the model abstract in a way that it does not hinder Gson to do its job whatever it heads in that problematic field:
#Getter #Setter
public class Response {
private String status;
#Getter #Setter
public static class DataItem {
private Long id;
// below 2 rows explained later, this is what changes
#JsonAdapter(AnswerDataDeserializer.class)
private AnswerData answerData;
}
private DataItem[] data;
}
As you see there is declared this AnswerData and #JsonAdapter for handling the actual more complex stuff:
public class AnswerDataDeserializer
implements JsonDeserializer<AnswerDataDeserializer.AnswerData> {
private final Gson gson = new Gson();
// The trick that makes the field more abstract. No necessarily
// needed answerData might possibly be just Object
public interface AnswerData {
// just to have something here not important
default String getType() {
return getClass().getName();
}
}
// here I have assumed Map<K,V> because of field name cannot be plain number.
#SuppressWarnings("serial")
public static class ChapterDataAnswer extends ArrayList<Map<Long, String>>
implements AnswerData {
}
#SuppressWarnings("serial")
public static class LiabilityDataAnswer
extends ArrayList<LiabilityDataAnswer.LiabilityData>
implements AnswerData {
#Getter #Setter
public static class LiabilityData {
private Long liabilityTypeId;
private Double monthlyPayment;
private Integer remainingMonth;
private String liabilityName;
private String name;
}
}
#Override
public AnswerData deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
if(json.isJsonArray()) {
try {
return gson.fromJson(json, ChapterDataAnswer.class);
} catch (Exception e) {
return gson.fromJson(json, LiabilityDataAnswer.class);
}
}
if(json.isJsonObject()) {
// do something else
}
return null;
}
}
I have above presented only the two more complex array types. But as you can see you will have to check/peek all the deserialized AnswerData in some way to determine the actual type in method deserialize
Now you need still need to know about different types of AnswerData. Maybe there are such types that collide in a way that you cannot determine the type.
NOTE: you can also always also deserialize whole stuff or any object as a Map or Object (Gson will make it LinkedHashMap if I remember correct)
Whether way you do it you still need to check the instance of the object after deserialization what it is and use cast.

The design of the input JSON is terrible and really hard to use.
Let me say that:
it mixes elements and collections for the answerData attributes with dozens of cons against it;
answer elements lack the type discriminator field so the deserialize must analyze each JSON tree to produce a valid deserialized object with another dozen of cons against it (including "there is no way to determine the exact type precisely" and "it may require too much memory because of JSON trees");
Some tools like OpenAPI/Swagger use the discriminator field to deserialize to a dedicated type without doing any heuristics.
Any won't work for you of course, as Gson has no even a single idea what those payloads are supposed to be deserialized to.
Since you didn't provide your mappings, I'll provide mine demonstrating an example idea of how such terrible JSON documents can be deserialized.
This also includes:
using Java 11 and Lombok instead of Kotlin (as it does not really matter as you stated in the notice);
mapping an answer with a list of answers even if the incoming JSON node contains an object instead of an array to unify all of that;
creating a deducing deserializer that naively does some "magic" to get rid of the bad JSON design.
To resolve the first issue, elements vs arrays/lists, I've found a ready-to-use solution right here at S.O.:
#RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class AlwaysListTypeAdapterFactory<E> implements TypeAdapterFactory {
#Nullable
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if (!List.class.isAssignableFrom(typeToken.getRawType())) {
return null;
}
final Type elementType = resolveTypeArgument(typeToken.getType());
#SuppressWarnings("unchecked")
final TypeAdapter<E> elementTypeAdapter = (TypeAdapter<E>) gson.getAdapter(TypeToken.get(elementType));
#SuppressWarnings("unchecked")
final TypeAdapter<T> alwaysListTypeAdapter = (TypeAdapter<T>) new AlwaysListTypeAdapter<>(elementTypeAdapter).nullSafe();
return alwaysListTypeAdapter;
}
private static Type resolveTypeArgument(final Type type) {
if (!(type instanceof ParameterizedType)) {
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
private static final class AlwaysListTypeAdapter<E> extends TypeAdapter<List<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
#Override
public void write(final JsonWriter out, final List<E> list) {
throw new UnsupportedOperationException();
}
#Override
public List<E> read(final JsonReader in) throws IOException {
final List<E> list = new ArrayList<>();
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
in.beginArray();
while ( in.hasNext() ) {
list.add(elementTypeAdapter.read(in));
}
in.endArray();
break;
case BEGIN_OBJECT:
case STRING:
case NUMBER:
case BOOLEAN:
list.add(elementTypeAdapter.read(in));
break;
case NULL:
throw new AssertionError("Must never happen: check if the type adapter configured with .nullSafe()");
case NAME:
case END_ARRAY:
case END_OBJECT:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
throw new AssertionError("Must never happen: " + token);
}
return list;
}
}
}
Next, for the item no. 2, a deducing type adapter factory might be implemented like this:
#RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class DeducingTypeAdapterFactory<V> implements TypeAdapterFactory {
public interface TypeAdapterProvider {
#Nonnull
<T> TypeAdapter<T> provide(#Nonnull TypeToken<T> typeToken);
}
private final Predicate<? super TypeToken<?>> isSupported;
private final BiFunction<? super JsonElement, ? super TypeAdapterProvider, ? extends V> deduce;
public static <V> TypeAdapterFactory create(final Predicate<? super TypeToken<?>> isSupported,
final BiFunction<? super JsonElement, ? super TypeAdapterProvider, ? extends V> deduce) {
return new DeducingTypeAdapterFactory<>(isSupported, deduce);
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if (!isSupported.test(typeToken)) {
return null;
}
final Map<TypeToken<?>, TypeAdapter<?>> cache = new ConcurrentHashMap<>();
final TypeAdapter<V> deducedTypeAdapter = new TypeAdapter<V>() {
#Override
public void write(final JsonWriter jsonWriter, final V value) {
throw new UnsupportedOperationException();
}
#Override
public V read(final JsonReader jsonReader) {
final JsonElement jsonElement = Streams.parse(jsonReader);
return deduce.apply(jsonElement, new TypeAdapterProvider() {
#Nonnull
#Override
public <TT> TypeAdapter<TT> provide(#Nonnull final TypeToken<TT> typeToken) {
final TypeAdapter<?> cachedTypeAdapter = cache.computeIfAbsent(typeToken, tt -> gson.getDelegateAdapter(DeducingTypeAdapterFactory.this, tt));
#SuppressWarnings("unchecked")
final TypeAdapter<TT> typeAdapter = (TypeAdapter<TT>) cachedTypeAdapter;
return typeAdapter;
}
});
}
}
.nullSafe();
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) deducedTypeAdapter;
return typeAdapter;
}
}
Basically, it does no deducing itself, and only delegates the filter and deducing jobs elsewhere using the Strategy design pattern.
Now let's assume your mappings are "general" enough (including using #JsonAdapter for Answer to coerce single elements to become lists):
#RequiredArgsConstructor(access = AccessLevel.PACKAGE, staticName = "of")
#Getter
#EqualsAndHashCode
#ToString
final class Response<T> {
#Nullable
#SerializedName("status")
private final String status;
#Nullable
#SerializedName("data")
private final T data;
}
#RequiredArgsConstructor(access = AccessLevel.PACKAGE, staticName = "of")
#Getter
#EqualsAndHashCode
#ToString
final class Answer {
#SerializedName("id")
private final int id;
#Nullable
#SerializedName("answerData")
#JsonAdapter(AlwaysListTypeAdapterFactory.class)
private final List<AnswerDatum> answerData;
}
#RequiredArgsConstructor(access = AccessLevel.PRIVATE)
abstract class AnswerDatum {
interface Visitor<R> {
R visit(#Nonnull Type1 answerDatum);
R visit(#Nonnull Type2 answerDatum);
R visit(#Nonnull Type3 answerDatum);
R visit(#Nonnull Type4 answerDatum);
}
abstract <R> R accept(#Nonnull Visitor<? extends R> visitor);
#RequiredArgsConstructor(access = AccessLevel.PACKAGE, staticName = "of")
#Getter
#EqualsAndHashCode(callSuper = false)
#ToString(callSuper = false)
static final class Type1 extends AnswerDatum {
#SerializedName("selectionOptionId")
private final int selectionOptionId;
#Nullable
#SerializedName("selectionOptionText")
private final String selectionOptionText;
#Override
<R> R accept(#Nonnull final Visitor<? extends R> visitor) {
return visitor.visit(this);
}
}
#RequiredArgsConstructor(access = AccessLevel.PACKAGE, staticName = "of")
#Getter
#EqualsAndHashCode(callSuper = false)
#ToString(callSuper = false)
static final class Type2 extends AnswerDatum {
#SerializedName("IsAffiliatedWithSeller")
private final boolean isAffiliatedWithSeller;
#Nullable
#SerializedName("AffiliationDescription")
private final String affiliationDescription;
#Override
<R> R accept(#Nonnull final Visitor<? extends R> visitor) {
return visitor.visit(this);
}
}
#RequiredArgsConstructor(access = AccessLevel.PACKAGE, staticName = "of")
#Getter
#EqualsAndHashCode(callSuper = false)
#ToString(callSuper = false)
static final class Type3 extends AnswerDatum {
#Nonnull
private final String key;
#Nullable
private final String value;
#Override
<R> R accept(#Nonnull final Visitor<? extends R> visitor) {
return visitor.visit(this);
}
}
#RequiredArgsConstructor(access = AccessLevel.PACKAGE, staticName = "of")
#Getter
#EqualsAndHashCode(callSuper = false)
#ToString(callSuper = false)
static final class Type4 extends AnswerDatum {
#SerializedName("liabilityTypeId")
private final int liabilityTypeId;
#SerializedName("monthlyPayment")
private final int monthlyPayment;
#SerializedName("remainingMonth")
private final int remainingMonth;
#Nullable
#SerializedName("liabilityName")
private final String liabilityName;
#Nullable
#SerializedName("name")
private final String name;
#Override
<R> R accept(#Nonnull final Visitor<? extends R> visitor) {
return visitor.visit(this);
}
}
}
Note how AnswerDatum uses the Visitor design pattern to avoid explicit type casting.
I'm not sure how it is leveraged in Java when using sealed classes.
public final class DeducingTypeAdapterFactoryTest {
private static final Pattern digitsPattern = Pattern.compile("^\\d+$");
private static final TypeToken<String> stringTypeToken = new TypeToken<>() {};
private static final TypeToken<AnswerDatum.Type1> answerDatumType1TypeToken = new TypeToken<>() {};
private static final TypeToken<AnswerDatum.Type2> answerDatumType2TypeToken = new TypeToken<>() {};
private static final TypeToken<AnswerDatum.Type4> answerDatumType4TypeToken = new TypeToken<>() {};
private static final Gson gson = new GsonBuilder()
.disableInnerClassSerialization()
.disableHtmlEscaping()
.registerTypeAdapterFactory(DeducingTypeAdapterFactory.create(
typeToken -> AnswerDatum.class.isAssignableFrom(typeToken.getRawType()),
(jsonElement, getTypeAdapter) -> {
if ( jsonElement.isJsonObject() ) {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
// type-1? hopefully...
if ( jsonObject.has("selectionOptionId") ) {
return getTypeAdapter.provide(answerDatumType1TypeToken)
.fromJsonTree(jsonElement);
}
// type-2? hopefully...
if ( jsonObject.has("IsAffiliatedWithSeller") ) {
return getTypeAdapter.provide(answerDatumType2TypeToken)
.fromJsonTree(jsonElement);
}
// type-3? hopefully...
if ( jsonObject.size() == 1 ) {
final Map.Entry<String, JsonElement> onlyEntry = jsonObject.entrySet().iterator().next();
final String key = onlyEntry.getKey();
if ( digitsPattern.matcher(key).matches() ) {
final String value = getTypeAdapter.provide(stringTypeToken)
.fromJsonTree(onlyEntry.getValue());
return AnswerDatum.Type3.of(key, value);
}
}
// type-4? hopefully...
if ( jsonObject.has("liabilityTypeId") ) {
return getTypeAdapter.provide(answerDatumType4TypeToken)
.fromJsonTree(jsonElement);
}
}
throw new UnsupportedOperationException("can't parse: " + jsonElement);
}
))
.create();
private static final TypeToken<Response<List<Answer>>> listOfAnswerResponseType = new TypeToken<>() {};
#Test
public void testEqualsAndHashCode() throws IOException {
final Object expected = Response.of(
"OK",
List.of(
Answer.of(
10,
null
),
Answer.of(
21,
List.of(
AnswerDatum.Type1.of(0, null)
)
),
Answer.of(
45,
List.of(
AnswerDatum.Type2.of(false, null)
)
),
Answer.of(
131,
List.of(
AnswerDatum.Type3.of("2", "Chapter 11"),
AnswerDatum.Type3.of("3", "Chapter 12"),
AnswerDatum.Type3.of("1", "Chapter 7")
)
),
Answer.of(
140,
List.of(
AnswerDatum.Type4.of(2, 200, 2, "Separate Maintenance", "Two"),
AnswerDatum.Type4.of(1, 300, 1, "Child Support", "Three")
)
)
)
);
try (final JsonReader jsonReader = openJsonInput()) {
final Object actual = gson.fromJson(jsonReader, listOfAnswerResponseType.getType());
Assertions.assertEquals(expected, actual);
}
}
#Test
public void testVisitor() throws IOException {
final Object expected = List.of(
"21:0",
"45:false",
"131:2:Chapter 11",
"131:3:Chapter 12",
"131:1:Chapter 7",
"140:Two",
"140:Three"
);
try (final JsonReader jsonReader = openJsonInput()) {
final Response<List<Answer>> response = gson.fromJson(jsonReader, listOfAnswerResponseType.getType());
final List<Answer> data = response.getData();
assert data != null;
final Object actual = data.stream()
.flatMap(answer -> Optional.ofNullable(answer.getAnswerData())
.map(answerData -> answerData.stream()
.map(answerDatum -> answerDatum.accept(new AnswerDatum.Visitor<String>() {
#Override
public String visit(#Nonnull final AnswerDatum.Type1 answerDatum) {
return answer.getId() + ":" + answerDatum.getSelectionOptionId();
}
#Override
public String visit(#Nonnull final AnswerDatum.Type2 answerDatum) {
return answer.getId() + ":" + answerDatum.isAffiliatedWithSeller();
}
#Override
public String visit(#Nonnull final AnswerDatum.Type3 answerDatum) {
return answer.getId() + ":" + answerDatum.getKey() + ':' + answerDatum.getValue();
}
#Override
public String visit(#Nonnull final AnswerDatum.Type4 answerDatum) {
return answer.getId() + ":" + answerDatum.getName();
}
})
)
)
.orElse(Stream.empty())
)
.collect(Collectors.toUnmodifiableList());
Assertions.assertEquals(expected, actual);
}
}
private static JsonReader openJsonInput() throws IOException {
return // ... your code code here ...
}
}
That's it.
I find it pretty difficult and unnecessarily complicated.
Please ask your server-side mates to fix their design for good (note how the current situation makes deserializing harder than it might be when designed well).

The Json response is wrong. There is no need to handle this response in client side, the Json response should be changed from the server side. Otherwise this is going to be a huge burden for you in future. A Json object should have a properly defined keys and its values.

Related

How to get inner JSON object with GSON/Retrofit when outer name is variable?

I have a JSON response from an API like this:
{"asalas77":
{"id":23519033,"name":"Asalas77","profileIconId":22,"revisionDate":1487214366000,"summonerLevel":30}
}
And I need to extract the inner object from it. I tried using a deserializer like shown in this question Get nested JSON object with GSON using retrofit but it doesn't work for me.
public class SummonerDeserializer implements JsonDeserializer<Summoner> {
#Override
public Summoner deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
long id = je.getAsJsonObject().get("id").getAsLong();
String name = je.getAsJsonObject().get("name").getAsString();
int profileIconId = je.getAsJsonObject().get("profileIconId").getAsInt();
long revisionDate = je.getAsJsonObject().get("revisionDate").getAsLong();
long summonerLevel = je.getAsJsonObject().get("summonerLevel").getAsLong();
Summoner s = new Summoner();
s.setId(id);
s.setName(name);
s.setProfileIconId(profileIconId);
s.setRevisionDate(revisionDate);
s.setSummonerLevel(summonerLevel);
return s;
}
}
But the problem is I can't access the inner fields from JsonElement je and the name asalas77 is a variable (it's a search query) so I can't extract the inner object directly.
You must have a wrapper class in order not to clash deserialization strategies. Assume it's as follows:
final class SummonerResponse {
private final Summoner summoner;
private SummonerResponse(final Summoner summoner) {
this.summoner = summoner;
}
static SummonerResponse summonerResponse(final Summoner summoner) {
return new SummonerResponse(summoner);
}
Summoner getSummoner() {
return summoner;
}
}
Then you can either create a custom response deserializer:
final class SummonerWrapperDeserializer
implements JsonDeserializer<SummonerResponse> {
private static final JsonDeserializer<SummonerResponse> summonerDeserializer = new SummonerWrapperDeserializer();
private SummonerWrapperDeserializer() {
}
static JsonDeserializer<SummonerResponse> getSummonerResponseDeserializer() {
return summonerDeserializer;
}
#Override
public SummonerResponse deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
// Pick the root as a JSON object
final JsonObject outerJsonObject = jsonElement.getAsJsonObject();
// And check how many properties does it have
final Iterable<? extends Entry<String, JsonElement>> outerJsonObjectEntries = outerJsonObject.entrySet();
if ( outerJsonObject.size() != 1 ) {
throw new JsonParseException("Expected one property object, the actual properties are: " + getPropertyName(outerJsonObjectEntries));
}
// If it has only one property, just get the property and take its inner value
final JsonElement innerJsonElement = outerJsonObjectEntries.iterator().next().getValue();
// Once it's obtained, just delegate the parsing to a downstream parser - no need to create Summoner instances by hands
return summonerResponse(context.deserialize(innerJsonElement, Summoner.class));
}
private static Set<String> getPropertyName(final Iterable<? extends Entry<String, JsonElement>> entries) {
final Set<String> keys = new LinkedHashSet<>();
for ( final Entry<String, JsonElement> entry : entries ) {
keys.add(entry.getKey());
}
return keys;
}
}
Or save some memory (the JSON (de)serializers require some memory because they work with JSON trees) and create a more low level type adapter:
final class SummonerResponseTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory summonerResponseTypeAdapterFactory = new SummonerResponseTypeAdapterFactory();
private SummonerResponseTypeAdapterFactory() {
}
static TypeAdapterFactory getSummonerResponseTypeAdapterFactory() {
return summonerResponseTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Check if we can handle SummonerResponse. Classes can be compared with `==`
if ( typeToken.getRawType() == SummonerResponse.class ) {
final TypeAdapter<SummonerResponse> typeAdapter = getSummonerResponseTypeAdapter(gson);
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
return castTypeAdapter;
}
return null;
}
}
final class SummonerResponseTypeAdapter
extends TypeAdapter<SummonerResponse> {
private final Gson gson;
private SummonerResponseTypeAdapter(final Gson gson) {
this.gson = gson;
}
static TypeAdapter<SummonerResponse> getSummonerResponseTypeAdapter(final Gson gson) {
return new SummonerResponseTypeAdapter(gson);
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final SummonerResponse summonerResponse)
throws IOException {
// The incoming object may be null
if ( summonerResponse == null && gson.serializeNulls() ) {
out.nullValue();
return;
}
// Generate the inner object
out.beginObject();
out.name(summonerResponse.getSummoner().name);
gson.toJson(summonerResponse.getSummoner(), Summoner.class, out);
out.endObject();
}
#Override
public SummonerResponse read(final JsonReader in)
throws IOException {
// is it a null?
if ( in.peek() == NULL ) {
return null;
}
// make sure that the inner read JSON contains an inner object
in.beginObject();
// ignore the name
in.nextName();
// delegate parsing to the backing Gson instance in order to apply downstream parsing
final Summoner summoner = gson.fromJson(in, Summoner.class);
// check if there are more properties within the inner object
if ( in.peek() == NAME ) {
throw new MalformedJsonException("Unexpected: " + in.nextName());
}
// consume the "}" token
in.endObject();
return summonerResponse(summoner);
}
}
Then any of the options above can be used like this:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(SummonerResponse.class, getSummonerResponseDeserializer())
.create();
final SummonerResponse summonerResponse = gson.fromJson(JSON, SummonerResponse.class);
final Summoner summoner = summonerResponse.getSummoner();
out.println(summoner.id + " => " + summoner.name);
or
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getSummonerResponseTypeAdapterFactory())
.create();
final SummonerResponse summonerResponse = gson.fromJson(JSON, SummonerResponse.class);
final Summoner summoner = summonerResponse.getSummoner();
out.println(summoner.id + " => " + summoner.name);
out.println(gson.toJson(summonerResponse));
The outputs are
23519033 => Asalas77
and
23519033 => Asalas77
{"Asalas77":{"id":23519033,"name":"Asalas77","profileIconId":22,"revisionDate":1487214366000,"summonerLevel":30}}
respectively.

Jackson JSON Deserialization of MongoDB ObjectId

Ok, so first off here's the JSON that's returning from my web service. I'm trying to deserialize it into pojos after an asynchronous query in a ResponseHandler in my Android ContentProvider.
{"exampleList" : [{
"locationId" : "00001" ,
"owners" : [
{
"paidID" : { "$oid" : "50a9c951300493f64fbffdb6"} ,
"userID" : { "$oid" : "50a9c951300493f64fbffdb6"}
} ,
{
"paidID" : { "$oid" : "50a9c951300493f64fbffdb7"} ,
"userID" : { "$oid" : "50a9c951300493f64fbffdb7"}
}
]
}]}
At first, I was confused about the problem I was seeing, since I use the same Jackson-annotated beans for my web service as I do in my Android app--but then I realized that the owners object was never getting sent in the sample JSON to my web service (it skips the POJOs on my web service and gets added into the documents in mongoDB through atomic updates from the DAO).
So OK. Up to now, Jackson wasn't having to handle the owners object, and now that it is it is choking on it, namely:
JsonMappingException: Can not deserialize instance of java.lang.String out of
START_OBJECT token at [char position where you can find "userID" and "paidID"]
through reference chain [path to my Jackson bean which contains the owners class]
My Jackson bean has a wrapper, which is what that "exampleList" is all about:
public class Examples extends HashMap<String, ArrayList<Example>> {
}
And then the actual Example class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Example implements Comparable<Example> {
#ObjectId #Id
private String id;
#JsonProperty(Constants.Example.location)
private String location;
#JsonProperty(Constants.Example.OWNERS)
private List<Owners> owners;
public int compareTo(Example _o) {
return getId().compareTo(_o.getId());
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public List<Example.Owners> getOwners() {
return owners;
}
public void setOwners(List<Example.Owners> owners) {
this.owners = owners;
}
public Example() {
}
#JsonCreator
public Example(#Id #ObjectId String id) {
this.id = id;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Owners implements Comparable<Owners> {
#JsonProperty(Constants.Example.USERID)
private String userID;
#JsonProperty(Constants.Example.PAIDID)
private String paidID;
public Owners() {
}
public int compareTo(Owners _o) {
return getUserID().compareTo(_o.getUserID());
}
#ObjectId
public String getUserID() {
return userID;
}
#ObjectId
public void setUserID(String userID) {
this.userID = userID;
}
#ObjectId
public String getPaidID() {
return paidID;
}
#ObjectId
public void setPaidID(String paidID) {
this.paidID = paidID;
}
}
}
And finally, the code in the ResponseHandler where this is all failing (the 2nd line produces the JsonMappingException):
objectMapper = MongoJacksonMapperModule.configure(objectMapper);
mExamples = objectMapper.readValue(jsonParser, Examples.class);
I have a feeling the issue is that Jackson still doesn't know how to map those $oid, which are the mongoDB ObjectIds. The MongoJacksonMapper library is supposed to help that by providing the #ObjectId annotation and a way to configure the ObjectMapper to use that library, but it still isn't working. For some reason, it's still looking for the userID or paidID as a String, not an ObjectId. Any ideas?
Another alternative is
com.fasterxml.jackson.databind.ser.std.ToStringSerializer.
#Id
#JsonSerialize(using = ToStringSerializer.class)
private final ObjectId id;
This will result in:
{
"id": "5489f420c8306b6ac8d33897"
}
For future users: Use a custom jackson deserializer to convert $oid back to ObjectId.
public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {
#Override
public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode oid = ((JsonNode)p.readValueAsTree()).get("$oid");
return new ObjectId(oid.asText());
}
}
How to use:
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
mapper.registerModule(mod);
YourClass obj = mapper.readValue("{your json with $oid}", YourClass.class);
My code had at least two problems that were pretty tough to track down answers to online, so I'll make sure to link here. Basically, child classes need a constructor in the parent class that calls Jackson's readValue() to map the child. As far as mongoDB $oid's go, you should create a separate MongoId class to represent these mongo objects, and follow a similar pattern as with the child class to map the data when it comes in for deserialization. Here's a blog post I found that describes this well and provides some examples.
Jackson does not know how to serialize an ObjectId. I tweaked Arny's code to serialize any ObjectId and provide this working example:
public class SerialiserTest {
private ObjectMapper mapper = new ObjectMapper();
public static class T {
private ObjectId objectId;
public ObjectId getObjectId() {
return objectId;
}
public void setObjectId(ObjectId objectId) {
this.objectId = objectId;
}
}
#Test
public final void serDeser() throws IOException {
T t = new T();
t.setObjectId(new ObjectId());
List<T> ls = Collections.singletonList(t);
String json = mapper.writeValueAsString(ls);
System.out.println(json);
SimpleModule mod = new SimpleModule("ObjectId", new Version(1, 0, 0, null, null, null));
mod.addDeserializer(ObjectId.class, new ObjectIdDeserializer());
mapper.registerModule(mod);
JavaType type = mapper.getTypeFactory().
constructCollectionType(List.class, T.class);
List<?> l = mapper.readValue(json, type);
System.out.println(l);
}
}
public class ObjectIdDeserializer extends JsonDeserializer<ObjectId> {
#Override
public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode n = (JsonNode)p.readValueAsTree();
return new ObjectId(n.get("timestamp").asInt(), n.get("machineIdentifier").asInt(), (short) n.get("processIdentifier").asInt(), n.get("counter").asInt());
}
}
There's an even easier way documented here which was a lifesaver for me. Now you can use the ObjectId in Java but when you go to/from JSON it'll be a String.
public class ObjectIdJsonSerializer extends JsonSerializer<ObjectId> {
#Override
public void serialize(ObjectId o, JsonGenerator j, SerializerProvider s) throws IOException, JsonProcessingException {
if(o == null) {
j.writeNull();
} else {
j.writeString(o.toString());
}
}
}
And then in your beans:
#JsonSerialize(using=ObjectIdJsonSerializer.class)
private ObjectId id;
I did it like this:
#Configuration
public class SpringWebFluxConfig {
#Bean
#Primary
ObjectMapper objectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializerByType(ObjectId.class, new ToStringSerializer());
builder.deserializerByType(ObjectId.class, new JsonDeserializer() {
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
Map oid = p.readValueAs(Map.class);
return new ObjectId(
(Integer) oid.get("timestamp"),
(Integer) oid.get("machineIdentifier"),
((Integer) oid.get("processIdentifier")).shortValue(),
(Integer) oid.get("counter"));
}
});
return builder.build();
}
}

Jackson in Spring: how to unmarshal a "generic" class w/o it thinking it's a LinkedHashMap?

So my entities look like this:
public class HappyClass<T>
{
private String id;
prviate int ver;
private Object obj;
public String getId()
{
return this.id;
}
public void setId( String id )
{
this.id = id;
}
public int getVer()
{
return this.ver;
}
public void setVer( int ver )
{
this.ver = ver;
}
#JsonTypeInfo( use = Id.NONE )
public T getObj()
{
return obj;
}
public void setObj( T obj )
{
this.obj = obj;
}
}
public class HappyGeneric
{
private String someStuff();
public String getSomeStuff()
{
return this.someStuff();
}
public void setSomeStuff( String someStuff )
{
this.someStuff = someStuff;
}
}
If I instantiate a class like this:
HappyClass<HappyGeneric> hc = new HappyClass<HappyGeneric>();
If I send it to Spring in a #ResponseBody it returns this:
{
"id" : "iamsomeid",
"ver" : 123,
"obj" : {
"someStuff" : "iamsomestuff"
}
}
However, when Spring and/or Jackson attempts to unmarshal the same JSON, it figures out that the main class is a HappyClass, however, the getObj() it unmarshals to a LinkedHashMap and not a HappyGeneric no matter what I seem to annotate it with.
Anybody have any ideas how I can force Jackson to unmarshal that generic to the original class?
Thanks!
EDIT: I'm aware I can call mapper.convertValue( blah.getObj(), HappyGeneric.class ) and get the object out that way-- I was hoping to get Spring to figure it out automatically (through annotations, for example).

jersey (+ jackson) map field serialization

I have a simple jersey web service and I'd like to consume / produce objects that contain map fields, like
#XmlElement
private Map<String,String> properties;
if this string goes into the web service,
{ properties: { key1: val1, key2: val2 )}
the properties field is deserialized as null with no errors. the same JSON goes in and out of GSON no problems, and in the short term I solved this by having jersey consume produce strings and using GSON to serialize / deserialize the JSON.
any ideas?
One option is to use annotated classes. So for instance a user might be represented by the following data.
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "user")
public class User {
private int uid;
public int user_id;
public String user_name;
public String email;
public URI image_url;
public List<User> friends;
public boolean admin;
public User() {
...
}
public User(final int userid) {
// Find user by id
}
}
If you return the User object as in the following piece of code, then jaxb will automatically serialize the List as a JSON list etc etc....
#GET
#Path("/{userid}")
#Produces("application/json", "application/xml")
public User showUser(#PathParam("userid") final int userid) {
return new User(userid);
}
Jersey uses JAXB for serialization. JAXB can not serialize a Map as there is no XML type for Java type Map. Also, Map is an interface and JAXB does not like interfaces.
If you are using JAXBJackson bridge to marshal, you will run into issue.
You will need to create an adapter like below and annotate your Map property with
#XmlJavaTypeAdapter(MapAdapter.class)
private Map<String,String> properties;
#XmlSeeAlso({ Adapter.class, MapElement.class })
public class MapAdapter<K,V> extends XmlAdapter<Adapter<K,V>, Map<K,V>>{
#Override
public Adapter<K,V> marshal(Map<K,V> map) throws Exception {
if ( map == null )
return null;
return new Adapter<K,V>(map);
}
#Override
public Map<K,V> unmarshal(Adapter<K,V> adapter) throws Exception {
throw new UnsupportedOperationException("Unmarshalling a list into a map is not supported");
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="Adapter", namespace="MapAdapter")
public static final class Adapter<K,V>{
List<MapElement<K,V>> item;
public Adapter(){}
public Adapter(Map<K,V> map){
item = new ArrayList<MapElement<K,V>>(map.size());
for (Map.Entry<K, V> entry : map.entrySet()) {
item.add(new MapElement<K,V>(entry));
}
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="MapElement", namespace="MapAdapter")
public static final class MapElement<K,V>{
#XmlAnyElement
private K key;
#XmlAnyElement
private V value;
public MapElement(){};
public MapElement(K key, V value){
this.key = key;
this.value = value;
}
public MapElement(Map.Entry<K, V> entry){
key = entry.getKey();
value = entry.getValue();
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}

Unmarshalling JSON array via Jettison/Resteasy

Ran into a similar problem like the following forum post:
http://jersey.576304.n2.nabble.com/parsing-JSON-with-Arrays-using-Jettison-td5732207.html
Using Resteasy 2.0.1GA with Jettison 1.2 and getting a problem marshalling arrays when involving namespace mappings. See code below. Basically if the number of array entries are greater than one and namespace mappings are used. Anybody else run into this problem? The Nabble form poster got around it by writing a custom unmarshaller.
I either need to isolate the Jettison bug or write a Resteasy extension of the JettisonMappedUnmarshaller class (which hands over the namespace mappings and unmarshaller to the Jettison Configuration).
The following code doesn't unmarshall (post step) if the properties variables contains 2 or more entries.
public class Experimenting {
#Path("test")
public static class MyResource {
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Property", propOrder = { "name", "value" })
public static class MyProperty {
#XmlElement(name = "Name", required = true)
protected String name;
#XmlElement(name = "Value", required = true)
protected String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#XmlType(name = "MyElement", propOrder = { "myProperty" })
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "MyElement", namespace = "http://www.klistret.com/cmdb/ci/commons")
#Mapped(namespaceMap = { #XmlNsMap(namespace = "http://www.klistret.com/cmdb/ci/commons", jsonName = "com.klistret.cmdb.ci.commons") })
public static class MyElement {
#XmlElement(name = "MyProperty", namespace = "http://www.klistret.com/cmdb/ci/commons")
protected List myProperty;
public List getMyProperty() {
if (myProperty == null) {
myProperty = new ArrayList();
}
return this.myProperty;
}
public void setMyProperty(List myProperty) {
this.myProperty = myProperty;
}
}
#GET
#Path("myElement/{id}")
#Produces(MediaType.APPLICATION_JSON)
public MyElement getMy(#PathParam("id")
Long id) {
MyElement myElement = new MyElement();
MyProperty example = new MyProperty();
example.setName("example");
example.setValue("of a property");
MyProperty another = new MyProperty();
another.setName("another");
another.setValue("just a test");
MyProperty[] properties = new MyProperty[] { example, another };
myElement.setMyProperty(Arrays.asList(properties));
return myElement;
}
#POST
#Path("/myElement")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public MyElement createMy(MyElement myElement) {
List properties = myElement.getMyProperty();
System.out.println("Properties size: " + properties.size());
return myElement;
}
}
private Dispatcher dispatcher;
#Before
public void setUp() throws Exception {
// embedded server
dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addPerRequestResource(MyResource.class);
}
#Test
public void getAndCreate() throws URISyntaxException,
UnsupportedEncodingException {
MockHttpRequest getRequest = MockHttpRequest.get("/test/element/44");
MockHttpResponse getResponse = new MockHttpResponse();
dispatcher.invoke(getRequest, getResponse);
String getResponseBodyAsString = getResponse.getContentAsString();
System.out.println(String.format(
"Get Response code [%s] with payload [%s]", getResponse
.getStatus(), getResponse.getContentAsString()));
MockHttpRequest postRequest = MockHttpRequest.post("/test/element");
MockHttpResponse postResponse = new MockHttpResponse();
postRequest.contentType(MediaType.APPLICATION_JSON);
postRequest.content(getResponseBodyAsString.getBytes("UTF-8"));
dispatcher.invoke(postRequest, postResponse);
System.out.println(String.format(
"Post Response code [%s] with payload [%s]", postResponse
.getStatus(), postResponse.getContentAsString()));
}
}
Do you have to use Jettison? If not I would recommend just switching to use Jackson instead; this typically solves array/list related problems (problem with Jettison is that it converts to XML model, which makes it very hard to tell arrays from objects -- there are bugs, too, but it is fundamentally hard thing to get working correctly).