Gson can't deserialize inherited class? - java

I have a simple Json structure like:
{"MessageType":"TimeData","TimeData":{"hh":12,"mm":13,"ms":15,"ss":14}}
and I devised the following classes to deserialize it:
public class JsonMessage
{
public enum MessageTypes{
WhoAreYou,
TimeData
}
JsonMessage(){
}
public MessageTypes MessageType;
}
class TimeData extends JsonMessage{
int hh;
int mm;
int ss;
int ms;
TimeData() {
}
}
I need to split deserialization into tow phases:
1- deserialize to read the MessageType.
2- proceed with the rest of deserialization based on the MessageType
The code is straightforward:
public void dispatch(Object message, IoSession session)
{
Gson gson = new Gson();
JsonMessage result = gson.fromJson(message.toString(), JsonMessage.class);
System.out.println(result.MessageType.toString());
switch (result.MessageType)
{
case WhoAreYou:{
//.....
break;
}
case TimeUpdate:
TimeData res = new Gson().fromJson(message.toString(), TimeData.class);
System.out.println(res.hh);
break;
default:break;
}
}
My Program can enter the correct switch-case(which is TimeUpdate) but it doesn't parse it correctly (The println prints 0 instead of 12)
where do you think I have done something wrong?
thank you

The issue is that your JSON represents an Object that contains another object you're interested in while your Java is just a single object.
You can actually just write deserializers for each type and use them once you determine the MessageType:
public static void main(String[] args)
{
Gson gson = new GsonBuilder().registerTypeAdapter(TimeData.class, new TimeDataDeserializer()).create();
String json = "{\"MessageType\":\"TimeData\",\"TimeData\":{\"hh\":12,\"mm\":13,\"ms\":15,\"ss\":14}}";
JsonMessage message = gson.fromJson(json, JsonMessage.class);
switch(message.MessageType)
{
case TimeData:
TimeData td = new GsonBuilder()
.registerTypeAdapter(TimeData.class, new TimeDataDeserializer())
.create()
.fromJson(json, TimeData.class);
td.MessageType = message.MessageType
System.out.println(td.hh);
break;
default:
break;
}
}
class TimeDataDeserializer implements JsonDeserializer<TimeData>
{
#Override
public TimeData deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
JsonObject jo = je.getAsJsonObject().getAsJsonObject("TimeData");
Gson g = new Gson();
return g.fromJson(jo, TimeData.class);
}
}

I managed to solve this similar problem by implementing a custom JsonDeserializer in the following way.
First you attach to your enum the subclasses based on the type and a method to retrieve the correct Class<?> according to the enum name:
enum MessageType {
WHO_ARE_YOU(WhoAreYou.class),
TIME_UPDATE(TimeUpdate.class);
public final Class<?> clazz;
MessageType(Class<?> clazz) { this.clazz = clazz; }
public static MessageType forName(String name) {
for (MessageType t : values())
if (name.equals(t.name()))
return t;
return NULL;
}
}
Then in the deserialize method I did the following:
public JsonMessage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonObject object = json.getAsJsonObject();
String kind = object.get("messageType").getAsString();
Class<?> clazz = MessageType.forName(kind).clazz;
JsonMessage result = null;
try {
result = (JsonMessage)clazz.newInstance();
Field[] fs = clazz.getFields();
for (Field f : fs) {
Object value = context.deserialize(object.get(f.getName()), f.getType());
if (value != null)
f.set(result, value);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
Everything is managed by reflection so that a correct object is created and then all fields are deserialized accordingly.
I had a complex hierarchy of objects so I preferred to go this way to let the gson deserializer manage everything. Of course you will need to register the serializer with the gson parser instance.
A NOTE: Your naming of things is quite incorrect according to Java standards. enum constants should be ALL_CAPITALIZED, enum class names should be singular (eg. MessageType). Instance variables should be camelcased (eg. messageType not MessageType)

Related

Java Gson to Json Conversion

I have a class with the following attributes,
public AnalyticsEventProperty(String eventID, String key, Object value, EventPropertyValueType valueType) {
this.eventID = eventID;
this.key = key;
this.value = value;
this.type = valueType();
}
This object is created and passed to an array of event properties, when I do the Json Conversion I get the output below:
{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","name":"app_start","propertyArrayList":[{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","key":"session_id","value":"69200430-95a0-4e14-9a36-67942917573d"}
I am getting 'key and 'value' used, I can see why, but how do I use the key and values as key and values i.e. "session_id":"69200430-95a0-4e14-9a36-67942917573d", bearing in mind that these key and values may have different property names depending on what is passed in the constructor.
When i create the String i am simply calling
String text_to_send = new Gson().toJson(events);
Where events is the ArrayList.
You can solve this by writing a custom TypeAdapterFactory which obtains the default adapter for your class (that is the reflection based one) and uses it to create an in-memory JSON representation in the form of a JsonObject. That JsonObject can then be modified to have the structure you expect; afterwards it has to be written to the JsonWriter:
class RewritingEventPropertyAdapterFactory implements TypeAdapterFactory {
public static final RewritingEventPropertyAdapterFactory INSTANCE = new RewritingEventPropertyAdapterFactory();
private RewritingEventPropertyAdapterFactory() {}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only consider AnalyticsEventProperty or subtypes
if (!AnalyticsEventProperty.class.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
TypeAdapter<JsonObject> jsonObjectAdapter = gson.getAdapter(JsonObject.class);
return new TypeAdapter<T>() {
#Override
public T read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Deserialization is not supported");
}
#Override
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
// Remove "key" and "value"
String eventKey = jsonObject.remove("key").getAsString();
JsonElement eventValue = jsonObject.remove("value");
// Add back an entry in the form of `"key": "value"`
jsonObject.add(eventKey, eventValue);
// Write the transformed JsonObject
jsonObjectAdapter.write(out, jsonObject);
}
};
}
}
You then have to register the factory with a GsonBuilder.
An alternative would be to perform the complete serialization of the class manually by directly writing the properties to the JsonWriter. This will most likely be a bit more performant, but is also more error-prone.

Unable to deserialize complex/dynamic JSON to Java classes using Gson

I've been trying to deserialize a JSON to Java classes using Gson, but the JSON structure is too complex for me to handle. The JSON looks like this (I've trimmed some of it because of repetitions):
{
"results":[
{
"openEHR-EHR-CLUSTER.encounter_channel.v0/items[at0001]/value<DV_TEXT>":{
"type":"DV_TEXT",
"name":{
"en":"Encounter channel"
},
"attrs":[
"value"
]
},
"openEHR-EHR-CLUSTER.monitoring_reason.v0/items[at0001]/value<DV_TEXT>":{
"type":"DV_TEXT",
"name":{
"en":"Monitoring reason"
},
"attrs":[
"value"
]
}
},
{
"163eee06-83a4-4fd8-bf65-5d6a3ef35ac5":{
"d5760d01-84dd-42b2-8001-a69ebaa4c2df":{
"date":"2020-08-06 09:45:31",
"cols":[
{
"type":"DV_TEXT",
"path":"openEHR-EHR-CLUSTER.encounter_channel.v0/items[at0001]/value<DV_TEXT>",
"values":[
{
"instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.encounter_channel.v0](0)/items[at0001](0)/value",
"value":"null"
}
]
},
{
"type":"DV_TEXT",
"path":"openEHR-EHR-CLUSTER.monitoring_reason.v0/items[at0001]/value<DV_TEXT>",
"values":[
{
"instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.monitoring_reason.v0](1)/items[at0001](0)/value",
"value":"null"
}
]
}
]
},
"fb366b72-d567-4d23-9f5f-356fc09aff6f":{
"date":"2020-08-06 10:02:26",
"cols":[
{
"type":"DV_TEXT",
"path":"openEHR-EHR-CLUSTER.encounter_channel.v0/items[at0001]/value<DV_TEXT>",
"values":[
{
"instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.encounter_channel.v0](0)/items[at0001](0)/value",
"value":"Consulta presencial"
}
]
},
{
"type":"DV_TEXT",
"path":"openEHR-EHR-CLUSTER.monitoring_reason.v0/items[at0001]/value<DV_TEXT>",
"values":[
{
"instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.monitoring_reason.v0](1)/items[at0001](0)/value",
"value":"Consulta"
}
]
}
]
}
}
}
],
"pagination":{
"max":20,
"offset":0,
"nextOffset":20,
"prevOffset":0
},
"timing":"475 ms"
}
The main JSON object has three fields: results, pagination and timing. I can deserialize the pagination and timing just fine, as they always have the same structure. I cannot properly deserialize the results though.
results is always a list of two different objects. The second object, in particular, is the most complex one, as its field names are not static. The UUID name references always change on each API response. For instance, the field named "163eee06-83a4-4fd8-bf65-5d6a3ef35ac5" might have another id in the next JSON response. Therefore, I cannot give it a proper field name in the corresponding Java class. The same goes for "d5760d01-84dd-42b2-8001-a69ebaa4c2df" and "fb366b72-d567-4d23-9f5f-356fc09aff6f" in this case.
Any ideas on how to properly deserialize this kind of JSON using Gson? I've tried a couple of different approaches, but nothing has truly worked so far.
In most recent attempt I tried to use the JsonDeserializer approach in order to differentiate the type of objects in the results list. My current implementation looks like this (getters and setters were hidden because of space):
QueryResponse.java
public class QueryResponse {
private List<Map<String, ResultInterface>> results;
private Pagination pagination;
private String timing;
}
Pagination.java
public class Pagination {
private Integer max;
private Integer offset;
private Integer nextOffset;
private Integer previousOffset;
}
ResultInterface.java
public interface ResultInterface {
}
ElementDefinition.java
public class ElementDefinition implements ResultInterface {
private String type;
private Name name;
private List<String> attrs;
}
Name.java
public class Name {
private String en;
private String es;
}
Compositions.java
public class Compositions implements ResultInterface {
private Map<String, Composition> compositions;
}
Composition.java
public class Composition {
private String date;
private List<Col> cols;
}
Col.java
public class Col {
private String type;
private String path;
private List<Value> values;
}
Value.java
public class Value {
private String instanceTemplatePath;
private String value;
private String magnitude;
private String units;
private String code;
private String terminology_id;
}
ResultInterfaceDeserializer.java
public class ResultInterfaceDeserializer implements JsonDeserializer<ResultInterface> {
#Override
public ResultInterface deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jObject = (JsonObject) json;
JsonElement typeObj = jObject.get("type");
if (typeObj != null) {
return context.deserialize(json, ElementDefinition.class);
} else {
return context.deserialize(json, Compositions.class);
}
}
}
I'm calling Gson like this:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(ResultInterface.class, new ResultInterfaceDeserializer());
Gson gson = builder.create();
QueryResponse queryResponse = gson.fromJson(externalJsonResponse, QueryResponse.class);
The problem with this implementation is that there is nothing named compositions in the JSON structure, thus the Compositions.java class is not correctly identified. I know I have to use Java structures like Map<String, SomeObject>, but the problem is that there are too many dynamically named Json fields here, and I cannot "grab" them if they have no fixed name identifier.
UPDATE
I managed to find a solution. I'd say it's actually a workaround and probably not the most clean or elegant solution.
The problem with my current implementation was that I was trying to "grab" a JSON field called compositions when in fact it didn't exist. So, I decided to manipulate the JSON and add that field myself (in the code).
I changed the deserializer class to:
public class ResultInterfaceDeserializer implements JsonDeserializer<ResultInterface> {
public String encloseJsonWithCompositionsField(JsonElement json) {
return "{\"compositions\":" + json.toString() + "}";
}
#Override
public ResultInterface deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jObject = (JsonObject) json;
if (jObject.get("type") != null) {
return context.deserialize(json, ElementDefinition.class);
} else {
JsonElement jsonWithCompositionsField = new JsonParser().parse(encloseJsonWithCompositionsField(json));
return context.deserialize(jsonWithCompositionsField, Compositions.class);
}
}
}
With this change, I can now "grab" the compositions field and get the data in Java POJOs.
You could probably solve this by registering an additional JsonDeserializer for Compositions:
public class CompositionsDeserializer implements JsonDeserializer<Compositions> {
public static final CompositionsDeserializer INSTANCE = new CompositionsDeserializer();
private CompositionsDeserializer() { }
#Override
public Compositions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Compositions compositions = new Compositions();
Map<String, Composition> compositionsMap = new HashMap<>();
compositions.compositions = compositionsMap;
JsonObject compositionsJson = json.getAsJsonObject();
for (Map.Entry<String, JsonElement> compositionEntry : compositionsJson.entrySet()) {
Composition composition = context.deserialize(compositionEntry.getValue(), Composition.class);
compositionsMap.put(compositionEntry.getKey(), composition);
}
return compositions;
}
}
And then register that deserializer on the GsonBuilder as well:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ResultInterface.class, new ResultInterfaceDeserializer())
.registerTypeAdapter(Compositions.class, CompositionsDeserializer.INSTANCE)
.create();

Deserialize nested object with GSON

I'm trying to deserialize the following structure
{ meta: { keywords: [a, b, c, d]} ... }
other valid structures are
{ meta: { keywords: "a,b,c,d"} ... }
and
{ meta: {keywords: "a"} ...}
I have this classes
public class Data {
#PropertyName("meta")
MetaData meta;
...
}
public class MetaData {
List<String> keywords;
...
}
and a custom deserializer
public static class CustomDeserilizer implements JsonDeserializer<MetaData>{
#Override
public MetaData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<String> keywords = null;
Gson gson = new Gson();
MetaData metaData = gson.fromJson(json, AppMetaData.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("keywords")) {
JsonElement elem = jsonObject.get("keywords");
if (elem != null && !elem.isJsonNull()) {
if (jsonObject.get("keywords").isJsonArray()) {
keywords = gson.fromJson(jsonObject.get("keywords"), new TypeToken<List<String>>() {
}.getType());
} else {
String keywordString = gson.fromJson(jsonObject.get("keywords"), String.class);
keywords = new ArrayList<String>();
list.addAll(Arrays.asList(keywordString.split(",")));
}
}
}
metaData.setKeywords(keywords);
}
Then I try to apply the deserilizer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new CustomDeserilizer())
.create();
But I get a parsing error , because is trying to deserialize Data instead of MetaData, how can I apply this deserializer to make it work right?
I solved it creating a deserializer for my class Data.
public static class DataDeserilizer implements JsonDeserializer {
#Override
public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
Data data = gson.fromJson(json, Data.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("meta")) {
JsonElement elem = jsonObject.get("meta");
if (elem != null && !elem.isJsonNull()) {
Gson gsonDeserializer = new GsonBuilder()
.registerTypeAdapter(MetaData.class, new CustomDeserilizer())
.create();
gsonDeserializer.fromJson(jsonObject.get("meta"), Data.class);
}
}
return data;
}
}
And
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new DataDeserilizer())
.create();
Pretty obvious, but is there a more elegant solution?
Firstly, rename your class to meta instead of metadata and make keywords String instead of List.Then use the following to map your JSonString into your object.
Gson gson = new GsonBuilder().create();
Meta meta = gson.from(yourJsonString,Meta.class);
In order to get keywords only, you need this.
JsonObject jsonObject = new JsonObject(yourJSonString);
String data = jsonObject.getJsonObject("meta").getString("keywords");
keywords is a JsonObject not an JsonArray so you can't directly map it
onto List. You can split the string to get keywords in an array.
String keywords[] = data.split(",");
Here's a concise solution that leverages Java inheritance to represent the nested structure; and therefore does not need to provide any actual instance member fields (mappings, etc) for capturing the nested String data that GSON maps.
Step 1: For readability, create an empty object to represent the nested mapping
public class StateRegionCitiesMap extends HashMap<String, List<String>> {
}
Step 2: Add the one line of actual code to do the mapping; no other serialize/deserialize logic to manage
protected void loadContent(JsonObject stateRegionsJsonObject) {
HashMap<String, StateRegionCitiesMap> stateRegionCitiesMap =
mGson.fromJson(
stateRegionsJsonObject,
new TypeToken<HashMap<String, StateRegionCitiesMap>>() {
}.getType()
);
}
Alternatively, you can skip the wrapper class altogether and just directly put <String, List<String>> in the GSON call. However, I find an explicit object helps to inform/remind whoever is reading the code, what the purpose is.
Example JSON:
The class StateRegionCitiesMap represents a multi-tier map structure for say:
[US State] -> [State-Region Key] -> [Sub-Region Key] -> CitiesArray[]
"CA": {
"Central CA": {
"Central Valley": [
"FRESNO",
"VISALIA"
],
"Sacramento Area": [
"SACRAMENTO",
"EL DORADO HILLS"
]
},
This suppose to achieve what you want easily. You should define an inner static class. You can keep nesting classes to define keywords as class Keywords, etc. Just remember to have a field in the containing class, i.e.
in your inner class have private Keywords keywords;
In your Main class:
Gson gson = new Gson();
Data data = gson.fromJson(SOME_JSON_STRING, Data.class);
In a class called Data:
public class Data {
private Meta meta;
static class Meta{
private String[] keywords;
}
}

Gson - JsonSerializer in TypeAdapterFactory or generic type in TypeAdapter

I am trying to write a TypeAdapterFactory for the JavaFX Types that I am using (different properties and observables). For the ObjectProperty i could write a JsonSerializer/Deserializer making use of a cast to ParamterizedType and then getting the ActualTypeArguments.
This is the code I use to achieve this:
public class ObjectPropertySerializer implements JsonSerializer<ObjectProperty>, JsonDeserializer<ObjectProperty> {
#Override
public ObjectProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Type objectType = ((ParameterizedType)typeOfT).getActualTypeArguments()[0];
return new SimpleObjectProperty(new GsonBuilder().create().fromJson(json, objectType));
}
#Override
public JsonElement serialize(ObjectProperty src, Type typeOfSrc, JsonSerializationContext context) {
Type objectType = ((ParameterizedType)typeOfSrc).getActualTypeArguments()[0];
return new GsonBuilder().create().toJsonTree(src.getValue(), objectType);
}
}
However I also need to use a TypeAdapterFactory because of nested classes/object and direct serialisation, that do not work when registering the serialisers directly to a GsonBuilder using .registerTypeAdapter().
This is the code of my factory so far (the other properties already work):
public class JFXGsonAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if ( BooleanProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new BooleanPropertyAdapter();
}
else if ( DoubleProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new DoublePropertyAdapter();
}
else if (IntegerProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new IntegerPropertyAdapter();
}
else if (ObjectProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new ObjectPropertyAdapter(gson);
}
else if ( StringProperty.class.isAssignableFrom(type.getRawType()) ) {
return (TypeAdapter<T>) new StringPropertyAdapter();
}
else {
return null;
}
}
}
I can't return an implementation of the serializer/deserializer interfaces here, but need to use an adapter class that extends TypeAdapter.
Here is my TypeAdapter implementation for ObjectProperty:
public class ObjectPropertyAdapter<T> extends TypeAdapter<ObjectProperty> {
private Gson gson;
TypeAdapter<JsonElement> elementAdapter;
public ObjectPropertyAdapter( Gson gson ) {
this.gson = gson;
elementAdapter = gson.getAdapter(JsonElement.class);
}
#Override
public void write(JsonWriter out, ObjectProperty value) throws IOException {
if ( value == null ) {
out.nullValue();
} else {
JsonElement jsonElement = gson.toJsonTree(value.get());
elementAdapter.write(out, jsonElement);
}
}
#Override
public ObjectProperty read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
} else {
// Read
return null; // tmp
}
}
}
However I am not sure how to get the type of the generic for ObjectProperty in the TypeAdapter and I do not know how to get it from the TypeToken either (since I could pass it to the adapter from the factory).
Summary of the question:
1) How do I cast an implementation of JsonSerializer/Deserializer to TypeAdapter
or
2) How can I get the generic type for e.g. ObjectProperty from the TypeToken?
or
3) Do I need to do something completely different to achieve this?
1) You can't. JsonSerializer and JsonDeserializer are the old converter way. New applications should use TypeAdapter, which has a superset of funcionality.
2) Assuming you mean JavaFX's ObjectProperty<T> and you want to extract T's class.
You can access the Type inside TypeToken easily with TypeToken#getType().
I will give the example with List<String> which is the same idea but easily testable.
TypeToken<List<String>> typeToken = new TypeToken<List<String>>(){};
System.out.println("typeToken.getRawType() = " + typeToken.getRawType());
Type type = typeToken.getType();
System.out.println("typeToken.getType() = " + type);
if (type instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
System.out.println("typeArguments = " + Arrays.toString(typeArguments));
}
Output result:
typeToken.getRawType() = interface java.util.List
typeToken.getType() = java.util.List<java.lang.String>
typeArguments = [class java.lang.String]
3) I think you are nearly there. ;-) Ah, with the TypeAdapter you don't think in terms of JsonObject or JsonElement (DOM style) but you build the object or the JSON representation on the fly (stream style).
Also, in your question you say
However I also need to use a TypeAdapterFactory because of nested
classes/object and direct serialisation, that do not work when
registering the serialisers directly to a GsonBuilder using
.registerTypeAdapter().
Maybe you need to use GsonBuilder#registerTypeHierarchyAdapter() and look for those nested classes. It will depend on your problem details.

converting json into java object for a array with mixed types

My json string looks like the following:
{
"text": ["foo",1,"bar","2",3],
"text1": "value1",
"ComplexObject": {
.....
}
}
I have a pojo defined like this:
class MyPojo {
List<String> text;
String text1;
ComplexObject complexObject;
}
I use google gson and am able to get my java object populated properly. The problem here is that the field text is an array of mixed types (string and int). So all the entries there are converted into String and i am not able to figure out which entries in the array is a string vs int. I cant use parseInt since the entries in the original array may have "2" as well as 3.
Is there a way for me to get the right instance type of the fields in my array after converting into java object.
SOLUTION
So i implemented the solution using gson the round about way using the JsonDeserializer. And then i tried using jackson. Guess what jackson supports serializing/deserializing the mixed array type by preserving the data types.
ObjectMapper mapper = new ObjectMapper();
MyPojo gmEntry = mapper.readValue(json, new TypeReference<MyPojo >(){});
And i can basically fetch the List<Object> and do an instanceof to check for the datatype.
Shame on you gson!!
By having a custom class and adding a type adapter u can manipulate the string (json.toString() returns with the '"' quotes, so you can see if its a string or not.
Output: (the classes seem correct)
class test.Main$StringPojo pojo{object=foo}
class test.Main$IntPojo pojo{object=1}
class test.Main$StringPojo pojo{object=bar}
class test.Main$StringPojo pojo{object=2}
class test.Main$IntPojo pojo{object=3}
public static void main(final String[] args){
String str = "{\n" +
" \"text\": [\"foo\",1,\"bar\",\"2\",3],\n" +
" \"text1\": \"value1\" }";
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(pojo.class, new JsonDeserializer<pojo>() {
#Override
public pojo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
return new IntPojo(Integer.parseInt(json.toString()));
} catch (Exception e) {
return new StringPojo(json.getAsString());
}
}
});
MyPojo myPojo = builder.create().fromJson(str, MyPojo.class);
for (pojo pojo : myPojo.text) {
System.out.println(pojo.getClass() + " " + pojo.object);
}
}
public static abstract class pojo{
protected Object object;
public pojo() {
}
#Override
public String toString() {
return "pojo{" +
"object=" + object +
'}';
}
}
public static class StringPojo extends pojo{
public StringPojo(String str) {
object = str;
}
}
public static class IntPojo extends pojo{
public IntPojo(int intt) {
this.object = intt;
}
}
public static class MyPojo {
List<pojo> text;
String text1;
}
As you wrote - you defined: List<String> text; but that list also contains integers.
Java is strongly typed, please consider to either declare the List as List<Object> (less preferable) or creating a JSON list that contains only a single type of variable (more preferable).
You can create an abstract class ItemType (for use as array item type) and inherits from it two wrapper classes: one for int type and another for string type.
abstract class ItemType {
protected Object value;
}
class IntType extends ItemType {
IntType(Integer value){
this.value = value;
}
}
class StringType extends ItemType {
IntType(String value){
this.value = value;
}
}
Try this List<ItemType> text;
The above situation can be achived by using TypeAdapter of Gson API.
Please follow : https://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Collection-with-Objects-of-Arbitrary-Types
Not sure if this is what you need, but this is the code I use for parsing JSON.
static public void newsParser(String urlString, String targetObject) throws FileNotFoundException, IOException
{
URL url = new URL(urlString);
JSONParser parser=new JSONParser();
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
Object obj;
try
{
obj = parser.parse(br);
//JSONObject jsonObject = (JSONObject) obj;
JSONArray jsonArray = (JSONArray) obj;
Iterator<?> i = jsonArray.iterator();
while (i.hasNext())
{
slide = (JSONObject) i.next();
newsInfo = (String)slide.get(targetObject);
System.out.println(newsInfo);
newsTitles.add(newsInfo);
}
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}

Categories