I have JSON response which looks like that:
{
"response":[
"Some number (for example 8091)",
{
"Bunch of primitives inside the first JSONObject"
},
{
"Bunch of primitives inside the second JSONObject"
},
{
"Bunch of primitives inside the third JSONObject"
},
... (and so on)
]
}
So it's an array with first integer element and other elements are JSONObject.
I don't need integer element to be parsed. So how do I handle it using GSON?
I would solve this problem by creating a custom JsonDeserializer and registering it to your Gson instance before parsing. This custom deserializer would be set up to handle both ints and real objects.
First you need to build up a series of model objects to represent the data. Here's a template for what that might look like:
private static class TopLevel {
#SerializedName("response")
private final List<ResponseElement> elements;
private TopLevel() {
this.elements = null;
}
}
private static class ResponseInteger implements ResponseElement {
private final int value;
public ResponseInteger(int value) {
this.value = value;
}
}
private static class ResponseObject implements ResponseElement {
#SerializedName("id")
private final String id;
#SerializedName("text")
private final String text;
private ResponseObject() {
this.id = null;
this.text = null;
}
}
private interface ResponseElement {
// marker interface
}
TopLevel and ResponseObject have private constructors because they are going to let Gson set their fields using reflection, while ResponseInteger has a public constructor because we're going to manually invoke it from our custom deserializer.
Obviously you will have to fill out ResponseObject with the rest of its fields.
The deserializer is relatively simple. The json you posted contains only two kinds of elements, and we'll leverage this. Each time the deserializer is invoked, it checks whether the element is a primitive, and returns a ResponseInteger if so (or a ResponseObject if not).
private static class ResponseElementDeserializer implements JsonDeserializer<ResponseElement> {
#Override
public ResponseElement deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonPrimitive()) {
return new ResponseInteger(json.getAsInt());
}
else {
return context.deserialize(json, ResponseObject.class);
}
}
}
To use this deserializer, you'll have to register it with Gson using the GsonBuilder object.
private static Gson getGson() {
return new GsonBuilder()
.registerTypeAdapter(ResponseElement.class, new ResponseElementDeserializer())
.create();
}
And that's it. Now you can use this Gson object to easily parse TopLevel objects!
public void parseJson() {
TopLevel t = getGson().fromJson(json, TopLevel.class);
for (ResponseElement element : t.elements) {
System.out.println(element);
}
}
8061
[450602: Поздравляем!]
[451700: С реакцией чата и рассуждениями Папани после рипа..]
[451578: Помним...Любим...Скорбим...<br>2107 забирает лучших]
[451371: Земля тебе пухом братишка]
[451332: Доигрался, минус 900 экзов<br><br>R I P]
[451269: ]
[451242: https://www.twitch.tv/arthas подрубка<br><br>evilpapech.ru - скидка 30% на футболки!]
[451217: ]
[451181: или так це жерстко?]
[451108: ]
I used these toString() methods, which I omitted above for brevity:
#Override
public String toString() {
return Integer.toString(value);
}
#Override
public String toString() {
return "[" + id + ": " + text + "]";
}
Try this
Gson gson = new Gson();
// Reading from a file.
Example example = gson.fromJson(new FileReader("D:\\content.json"), Example.class);
POJO
package com.example;
public class Example {
private List<Integer> response = null;
public List<Integer> getResponse() {
return response;
}
public void setResponse(List<Integer> response) {
this.response = response;
}
}
Basically this structure is the wrong format for JSON data.
You need to remove the number, or put this number as a field in the same object like the one below (call ObjectA) and consider this is an array of ObjectA.
Then everything should work well. Try the code below:
public class Response {
#SerializedName("response")
#Expose
public List<ObjectA> objectA = null;
}
public class ObjectA {
#SerializedName("value")
#Expose
public Integer value;
#SerializedName("description")
#Expose
public String description;
}
Response response = new Gson().fromJson(responseString, Response.class);
Please use below ValueObject format which doesn't parse first integer element
public class ResponseVO {
public List<Response> response = new ArrayList();
public class Response {
public final long id;
public final long from_id;
...
}
}
Related
I'm using a very strange api, it's data field type is dynamic.
If error occured, the data field will be a string like this:
{
"code": 2001,
"data": "Error!"
}
And if success, the data field will be a object:
{
"code": 2000,
"data": {
"id": 1,
"name": "example"
}
}
I'm using the following Kotlin code to de-serialize it:
return Gson().fromJson(data, SimpleModel::class.java)
The model definition is down below:
import com.google.gson.annotations.SerializedName
class SimpleModel {
#SerializedName("code")
val code = 0
#SerializedName("data")
val data: SimpleData = SimpleData()
}
class SimpleData {
#SerializedName("id")
val id = ""
#SerializedName("name")
val name = ""
}
When no error occred, the code above works just fine. However when error occured, exception was thrown:
java.lang.IllegalStateException: Excepted BEGIN_OBJECT but was STRING at line 1 column x $path.data
Is there a way to de-serialize data field to an object or just anything and determine it's type by code manually?
You would need to write a custom deserializer, which decides what type to deserialize data into, depending on the runtime type of the node, and register that deserializer with your Gson instance. Unfortunately i am not familiar with kotlin syntax, so i can only give you pseudo code.
Field data in SimpleModel should be either Object, or make the class generic - SimpleModel<T>, and the field should be of type T as well.
Parse the input to gson's node type - JsonElement.
Get data field
JsonElement root = parseResponse();
root.getAsJsonObject().get("data").getAsString();
Use getAs...() methods to check type.
Get as string. If success, it's a string and set the string value in SimpleModel.
If you get exception getting as string, get it as object - getAsJsonObject(), parse the object to SimpleData and set this new object in SimpleModel.
You could use my my answer here as inspiration. Although it's about object mapper, it does the same thing - decides object type depending on the node type, and follows roughly the same algorithm i described above.
Also this guide has info about how to write yor own deserialzer and registering it.
Like the previous answer, I am not familiarized with Kotlin, and the following solution is in Java, but as I know it is easy to convert Java to Kotlin using IntelliJ built-in tools.
The success/error objects pair is a classic problem, and you can create your own way to solve it, but let's consider the following classes represent the success and error objects respectively (Java 17, pattern matching on switch enabled then):
abstract sealed class SimpleModel<T>
permits SimpleModelSuccess, SimpleModelError {
#SerializedName("code")
final int code;
SimpleModel(final int code) {
this.code = code;
}
}
final class SimpleModelSuccess<T>
extends SimpleModel<T> {
#SerializedName("data")
final T data;
private SimpleModelSuccess(final int code, final T data) {
super(code);
this.data = data;
}
}
final class SimpleModelError<T>
extends SimpleModel<T> {
#SerializedName("data") // the annotation is helping here!
final String message;
private SimpleModelError(final int code, final String message) {
super(code);
this.message = message;
}
}
The code above can explain itself. Now the core part that required more work than I thought before by providing you my first comment that appeared incomplete.
#RequiredArgsConstructor(access = AccessLevel.PRIVATE)
final class SimpleModelTypeAdapterFactory
implements TypeAdapterFactory {
#Getter
private static final TypeAdapterFactory instance = new SimpleModelTypeAdapterFactory();
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !SimpleModel.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// let's figure out what the model is parameterized with
final Type type = typeToken.getType();
final Type typeParameter;
if ( type instanceof ParameterizedType parameterizedType ) {
typeParameter = parameterizedType.getActualTypeArguments()[0];
} else {
throw new UnsupportedOperationException("Cannot infer type parameter from " + type);
}
// then borrow their respective type adapters for both success and error cases
#SuppressWarnings("unchecked")
final TypeAdapter<T> successDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelSuccess.class, typeParameter));
#SuppressWarnings("unchecked")
final TypeAdapter<T> errorDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelError.class, typeParameter));
return new TypeAdapter<>() {
#Override
public void write(final JsonWriter out, final T value) {
throw new UnsupportedOperationException();
}
#Override
public T read(final JsonReader in) {
// buffer the JSON tree first
// note that this solution may be very inefficient under some circumstances
final JsonObject buffer = Streams.parse(in).getAsJsonObject();
final JsonElement dataElement = buffer.get("data");
// is it's data is {...}, the consider it is success (by the way, what is code about?)
if ( dataElement.isJsonObject() ) {
return successDelegate.fromJsonTree(buffer);
}
// if it's a primitive, consider it's an error
if ( dataElement.isJsonPrimitive() ) {
return errorDelegate.fromJsonTree(buffer);
}
// well we've done our best...
throw new JsonParseException(String.format("Cannot deduce the model for %s", buffer.getClass()));
}
};
}
}
public final class SimpleModelTypeAdapterFactoryTest {
private static final class SomeJsonProvider
implements ArgumentsProvider {
#Override
public Stream<? extends Arguments> provideArguments(final ExtensionContext context) {
return Stream.of(
Arguments.of(
"""
{
"code": 2000,
"data": {
"id": 1,
"name": "example"
}
}
"""
),
Arguments.of(
"""
{
"code": 2001,
"data": "Error!"
}
"""
)
);
}
}
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#ToString
private static final class SimpleData {
private final String id;
private final String name;
}
private static final Type simpleModelOfSimpleDataType = TypeToken.getParameterized(SimpleModel.class, SimpleData.class)
.getType();
#ParameterizedTest
#ArgumentsSource(SomeJsonProvider.class)
public void test(final String json) {
final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.registerTypeAdapterFactory(SimpleModelTypeAdapterFactory.getInstance())
.create();
final SimpleModel<SimpleData> model = gson.fromJson(json, simpleModelOfSimpleDataType);
switch ( model ) {
case SimpleModelSuccess<SimpleData> success -> System.out.println(success.data);
case SimpleModelError<SimpleData> error -> System.out.println(error.message);
}
}
}
Here is what it prints to stdout:
SimpleModelTypeAdapterFactoryTest.SimpleData(id=1, name=example)
Error!
Well, yeah, this is a "bit" more tricky than it was suggested by my first comment.
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();
I'm working on a project that communicates with an API using JSON. This is my first attempt at JSON and I've been away from java for a few/several years, so please bear with me.
Here is an idea of what the data looks like:
String 1:
[{
"apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
String 2:
[{
"apicall2":
[{
"thatField":"thatFieldData",
"someFieldsAreTheSame":"someFieldsAreTheSameData",
"otherFieldsAreNotTheSame":"otherFieldsAreNotTheSame"
}]
}]
As you can see from my data example, the API returns a JSON string that contains the api used. The array inside contains the data. The API's have a lot of data fields in common but they are unrelated beyond that.
EDIT: There are dozens of these API's types that will need to be handled.
What I am trying to do is create a response class that accepts all of the JSON strings and returns an object containing the appropriate data.
For Example:
Gson gson = new Gson(); //Custom TypeAdapter goes here if needed.
Response apicall2 = gson.fromJson(apicall2String, Response.class);
System.out.println(apicall2.thatField); //Prints thatFieldData
System.out.println(apicall2.someFieldsAreTheSame); //Prints someFieldsAreTheSameData
System.out.println(apicall2.otherFieldsAreNotTheSame); //Prints otherFieldsAreNotTheSameData
This is where I am lost. Here is what I have so far. I think I need to use a TypeAdapter here but haven't been able to figure how to apply that to my case.
public class Response { //Change to TypeAdapter possibly?
}
public class apicall1 {
String thisField;
String thatField;
String anotherField;
}
public class apicall2 {
String thatField;
String someFieldsAreTheSame;
String otherFieldsAreNotTheSame;
}
You can use Gson's TypeToken class to deserialize json into object. Below is an example:
JSON:
[{ "apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
Model:
class Response{
private List<Result> apicall1;
class Result{
private String thisField;
private String thatField;
private String anotherField;
public String getThisField() {
return thisField;
}
public void setThisField(String thisField) {
this.thisField = thisField;
}
public String getThatField() {
return thatField;
}
public void setThatField(String thatField) {
this.thatField = thatField;
}
public String getAnotherField() {
return anotherField;
}
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
}
public List<Result> getApicall1() {
return apicall1;
}
public void setApicall1(List<Result> apicall1) {
this.apicall1 = apicall1;
}
}
Converter:
public static void main(String[] args) {
String response = "[{ \"apicall1\": [{ \"thisField\":\"thisFieldData\", \"thatField\":\"thatFieldData\", \"anotherField\":\"anotherFieldData\" }]}]";
Gson gson = new Gson();
List<Response> responses = gson.fromJson(response, new TypeToken<List<Response>>(){}.getType());
System.out.println(responses.get(0).getApicall1().get(0).getThisField());
}
I don't know if you want both adapters in one class. Might not be the best OOP design.
To achieve it you would need to do something like so:
public class DoublyTypeAdapter implements JsonDeserializer<ApiCallTypeParent>
{
Gson gson = new Gson();
#Override
public ApiCallTypeParent deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject json = jsonElement.getAsJsonObject();
ApiCallTypeParent desrializeIntoMe;
// Detect which type to implement
if(apiTypeOne(type) {
desrializeIntoMe = new TypeOne();
} else {
desrializeIntoMe = new TypeTwo();
}
for (Map.Entry<String, JsonElement> entry : json.entrySet())
{
switch(entry.getKey()){
case "thisField":
desrializeIntoMe.setThisField(entry.getValue().getAsString());
break;
......
default: // We don't care
break;
}
}
return desrializeIntoMe ;
}
}
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();
}
}
Basically, I marshall on one server into JSON, then send it to another server, where it should be unmarshalled. I use a response object called list wrapper, so that if there are any errors I can pass them along. With the JSON marshalled below by badgerfish(jettison), in a resteasy class, then returned to the other server, GSON will unmarshall to a listwrapper object, but the list inside is null. Any ideas?
Note: The list must remain generic because different objects may go into the list, though the list will always only have one type in it at a time.
Json
unmarshalling
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
Object List;
if (!JSON.equals("")) {
List = gson.fromJson(new BufferedReader(new StringReader(JSON)), ListWrapper.class);
}
Listwrapper type
#XmlRootElement(name = "ListWrapper")
public class ListWrapper {
private Vector<Object> objects;
private String status;
private int batch;
private ValidationException e;
public ListWrapper() {
this.setStatus("Success");
}
public ListWrapper(Vector<Object> list) {
this.setStatus("Success");
this.objects = list;
}
public ListWrapper(int x) {
this.setStatus("batch");
this.batch = x;
}
public Vector<Object> getList() {
return objects;
}
public void setList(Vector<Object> object) {
this.objects = object;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
#XmlJavaTypeAdapter(ThrowableAdapter.class)
public ValidationException getE() {
if (e != null) {
return e;
} else {
return null;
}
}
public void setE(ValidationException x) {
this.e = x;
}
public int getBatch() {
return batch;
}
public void setBatch(int batch) {
this.batch = batch;
}
}
You cannot deserialize using directly ListWrapper, you need a container class, this is why you list is empty. By the way, your list is not a list but a map instead (curly braces limit content of list).
I created a code that correctly parse your JSON and provides you with some simple functionality to extract data. Since you want to keep flexible the data you are passing between the servers, you will need strings to access to data.
Here is the code ready to copy and paste to try it by yourself. Keep in mind that accessor methods are based on structure you are showing in the example. I provided you in main 4 different kind of data you can extract from it. Let me know if you need more information about that.
package stackoverflow.questions.q19817221;
import java.util.*;
import com.google.gson.Gson;
public class Q19817221 {
public class ListWrapper {
private Map list;
private Map status;
private Map batch;
private Object extractValue(Map m) {
return m.get("$");
}
public Integer getBatch() {
return Integer.valueOf( (String) extractValue(batch));
}
public Object getValueFromList(String key) {
try {
Map m = (Map) list.get(key);
if (m != null)
return extractValue(m);
} catch (Exception e) {
return list.get(key);
}
return null;
}
public Object getValueFromList(String secondLevelKey, String key) {
Map secondLevelMap = (Map) list.get(secondLevelKey);
try {
Map m = (Map) secondLevelMap.get(key);
if (m != null)
return extractValue(m);
} catch (Exception e) {
return list.get(key);
}
return null;
}
}
public class Container {
public ListWrapper ListWrapper;
}
/**
* #param args
*/
public static void main(String[] args) {
String json = "{\"ListWrapper\":{\"batch\":{\"$\":\"0\"},\"list\":{\"#xmlns\":{\"xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},\"#xsi:type\":\"fidsUsers\",\"createdBy\":{\"$\":\"administrator\"},\"createdWhen\":{\"$\":\"2013-02-25T17:29:19-05:00\"},\"endDate\":{\"$\":\"2016-10-28T00:00:00-04:00\"}," +
"\"isDisabled\":{\"$\":\"N\"},\"previousPasswords\":{\"$\":\"HXQDa4WxTdBmZtvhMVTgnw==####zW6bdHkKdMN2p6CgRNjNHA==####Sim7JN3kaHoXnh3KUS2++Q==####Emz7zU0Wrm0lyb/K522O5A==##ZirxzRl28JqfjOzIaMzAog==\"}," +
"\"primaryKey\":{\"$\":\"David\"},\"pswdChgDate\":{\"$\":\"2013-07-12T08:27:46-04:00\"},\"pswdCount\":{\"$\":\"0\"},\"roleId\":{\"$\":\"Admin\"},\"roleIdFidsRoles\":{\"globalAccess\":{\"$\":\"Y\"},\"primaryKey\":{\"$\":\"Admin\"},\"roleDesc\":{\"$\":\"Administrator\"},\"roleId\":{\"$\":\"Admin\"}," +
"\"updatedBy\":{\"$\":\"David\"}},\"startDate\":{\"$\":\"1992-07-28T00:00:00-04:00\"},\"updatedBy\":{\"$\":\"David\"},\"updatedWhen\":{\"$\":\"2013-10-02T10:46:31-04:00\"},\"userId\":{\"$\":\"David\"},\"userName\":{\"$\":\"David3\"},\"userPassword\":{\"$\":\"HXQDa4WxTdBmZtvhMVTgnw==\"}},\"status\":{\"$\":\"Success\"}}}";
Container c = new Gson().fromJson(json, Container.class);
ListWrapper lw = c.ListWrapper;
System.out.println("batch:" + lw.getBatch());
System.out.println("createdBy:" + lw.getValueFromList("createdBy"));
System.out.println("#xsi:type: " + lw.getValueFromList("#xsi:type"));
System.out.println("roleIdFidsRoles\\primaryKey: " + lw.getValueFromList("roleIdFidsRoles", "primaryKey"));
}
}
This is execution result:
batch:0
createdBy:administrator
#xsi:type: fidsUsers
roleIdFidsRoles\primaryKey: Admin
By looking into your ListWrapper class and a json you have posted - you have a mismatch and that is probably why it failed to unmarshall.
For:
{"ListWrapper":{"batch":{"$":"0"},"list":{"#xmlns":{"xsi":"http:\/\/www.w3.org\/2001\/XMLSchema-instance"},"#xsi:type":"fidsUsers","createdBy":{"$":"administrator"},"createdWhen":{"$":"2013-02-25T17:29:19-05:00"},"endDate":{"$":"2016-10-28T00:00:00-04:00"},"isDisabled":{"$":"N"},"previousPasswords":{"$":"HXQDa4WxTdBmZtvhMVTgnw==####zW6bdHkKdMN2p6CgRNjNHA==####Sim7JN3kaHoXnh3KUS2++Q==####Emz7zU0Wrm0lyb\/K522O5A==##ZirxzRl28JqfjOzIaMzAog=="},"primaryKey":{"$":"David"},"pswdChgDate":{"$":"2013-07-12T08:27:46-04:00"},"pswdCount":{"$":"0"},"roleId":{"$":"Admin"},"roleIdFidsRoles":{"globalAccess":{"$":"Y"},"primaryKey":{"$":"Admin"},"roleDesc":{"$":"Administrator"},"roleId":{"$":"Admin"},"updatedBy":{"$":"David"}},"startDate":{"$":"1992-07-28T00:00:00-04:00"},"updatedBy":{"$":"David"},"updatedWhen":{"$":"2013-10-02T10:46:31-04:00"},"userId":{"$":"David"},"userName":{"$":"David3"},"userPassword":{"$":"HXQDa4WxTdBmZtvhMVTgnw=="}},"status":{"$":"Success"}}} to me it is a object ListWrapper that has 3 fields named: batch, list and status, where batch is an object with one field being a number, list is a map and status is an object having one string in it.
I am not familiar with badgerfish, having said that, in your ListWrapper try to change private Vector<Object> objects to private Map<String,Object> objects (and of course matching getters/setters) and see if that will work
The list must remain generic because different objects may go into
the list, though the list will always only have one type in it at a
time.
If you know what type is it before un-marshalling, then ONLY it can work through the use of TypeToken
Sample code :
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
Also, the definition will change to something like this :
public class ListWrapper {
private Vector<T> objects;
private String status;
private int batch;
}
or
public class ListWrapper {
private Vector<T extends someBaseObject> objects;
private String status;
private int batch;
}
depends on how exactly you decide to implement.