I am trying to deserialize a JSON data to a POJO.
The issue is that the list object is coming as a string, and gson gives an IllegalStateExceptioState. How can I parse the string as a list to an ArrayList using gson?
JSON DATA
{
"report_id":1943,
"history_id":3302654,
"project_id":null,
"owner_emails":"[\"abcd#xyz.com\"]",
"message":"Array\n(\n [name] => SOMENAME\n [age] => 36\n [gender] => male\n)\n"
}
POJO:
public class EventData {
private static Gson gson = new Gson();
#SerializedName("report_id")
public String reportID;
#SerializedName("history_id")
public String historyID;
#SerializedName("project_id")
public String projectID;
#SerializedName("owner_emails")
public ArrayList<String> ownerEmails = new ArrayList<String>();
#SerializedName("message")
public String message;
#SerializedName("title")
public String title;
public CrawlerNotifiedEventData(){
this.projectID = "Undefined";
this.reportID = "Undefined";
this.historyID = "Undefined";
this.title = "";
}
public String toJson(boolean base64Encode) throws java.io.UnsupportedEncodingException{
String json = gson.toJson(this, CrawlerNotifiedEventData.class);
if(base64Encode)
return Base64.getEncoder().encodeToString(json.getBytes("UTF8"));
return json;
}
public String toJson() throws java.io.UnsupportedEncodingException{
return this.toJson(false);
}
public static EventData builder(String json){
return gson.fromJson(json, EventData.class);
}
}
Deserialization:
EventData eventData = EventData.builder(json);
While deserializing i get the following error
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 252 path $.owner_emails
Boxing structured data in a string where it is unnecessary is a very common design issue across different serialization approaches. Fortunately, Gson can deal with fields like owner_emails (but not message of course).
Merely create a type adapter factory than can create a type adapter for a particular type by substituting the original one and doing a bit of more work. The adapter is supposed to read the payload as string and delegate the string deserialization to the type adapter it substitutes.
public final class JsonStringBoxTypeAdapterFactory
implements TypeAdapterFactory {
private JsonStringBoxTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final TypeAdapter<T> adapter = gson.getAdapter(typeToken);
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value) {
throw new UnsupportedOperationException(); // TODO
}
#Override
public T read(final JsonReader in)
throws IOException {
return adapter.fromJson(in.nextString());
}
};
}
}
#AllArgsConstructor
#ToString
#EqualsAndHashCode
final class EventData {
#SerializedName("owner_emails")
#JsonAdapter(JsonStringBoxTypeAdapterFactory.class)
List<String> ownerEmails;
}
The unit test below will be green:
final EventData eventData = gson.fromJson(json, EventData.class);
Assertions.assertEquals(new EventData(ImmutableList.of("abcd#xyz.com")), eventData);
That's it.
"owner_emails" is curently a string as follows
"owner_emails":"[\"abcd#xyz.com\"]"
It should be
"owner_emails": ["abcd#xyz.com"]
to be considered as array. You can manually remove the quotes and parse it.
Or you can parse it using JsonElement in Gson
You can use ObjectMapper from jackson library for this conversion.
Sample code of conversion::
public <T> T mapResource(Object resource, Class<T> clazz) {
try {
return objectMapper.readValue(objectMapper.writeValueAsString(resource), clazz);
} catch (IOException ex) {
throw new Exception();
}
}
Modify the model for a list like::
public class Reportdata{
private List<String> owner_emails = new ArrayList();
#JsonDeserialize(contentAs = CustomClass.class)
private List<CustomClass> customClassList = new ArrayList();
....// setter and getter
}
In addition to this, while creating the ObjectMapper object you can pass or register the module/ your custom module for deserialization in object like below.
objectMapper.setDefaultPropertyInclusion(Include.NON_EMPTY);
objectMapper.disable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
objectMapper.registerModule(new JavaTimeModule());
Related
I have data returned from DB using the below method (method from spark-java framework) below:
get("/data_on_page_load", "application/json", (Request request, Response response) -> {
List<Post> list = Post.findAll(); // NEED TO SERIALIZE THE RESPONSE
System.out.println("list is " + list);
return (list);
}, new JsonTransformer());
Data returned from DB:
[Model: com.soul.seeker.models.Post, table: 'post', attributes: {created_at=2017-03-26 04:06:35.0, details=aaa, id=36, title=Eventsa, url=eventsa, userImage=assets/img/spiritual-icon4.png, username=null}]
Where Post.findAll(); is method from http://javalite.io/record_selection#finding-all-records to get all records
Model: com.soul.seeker.models.Post is the POJO class below:
public class Post extends Model{
private String title;
private String details;
private String username;
private String userImage;
private String url;
private List categories;
//Getters and Setters removed for brevity
}
I am trying to serialize the out put using GSON TypeToken and TypeAdapter
ClassTypeAdapterFactory:
public class ClassTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if(!Class.class.isAssignableFrom(typeToken.getRawType())) {
return null;
}
return (TypeAdapter<T>) new ClassTypeAdapter();
}
}
ClassTypeAdapter:
public class ClassTypeAdapter extends TypeAdapter<Class<?>> {
#Override
public void write(JsonWriter jsonWriter, Class<?> clazz) throws IOException {
if(clazz == null){
jsonWriter.nullValue();
return;
}
jsonWriter.value(clazz.getName());
}
#Override
public Class<?> read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
return null;
}
Class<?> clazz = null;
try {
clazz = Class.forName(jsonReader.nextString());
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return clazz;
}
}
Here I am using JsonTransformer which implements ResponseTransformer from spark-java ResponseTransformer interface
public class JsonTransformer implements ResponseTransformer {
private Gson gson = new Gson();
#Override
public String render(Object model) {
GsonBuilder gsonBuilder = new GsonBuilder();
gson = gsonBuilder.registerTypeAdapterFactory(new ClassTypeAdapterFactory()).create();
return gson.toJson(model);
}
}
Now the problem is the json out put also contains serialized class instead of only returning List object of pojo class. I am new to Java & Gson serialization.
Can any one please help in implementing TypeAdapter to return only List object? for Example like this:
Type listOfTestObject = new TypeToken<List<TestObject>>(){}.getType();
String s = gson.toJson(list, listOfTestObject);
List<TestObject> list2 = gson.fromJson(s, listOfTestObject);
I think that you won't need to type a Response Transformer, you can use a new Gson object directly like the last snippet at the response transformer documentation. It would end up like this:
get("/data_on_page_load", (req, res) -> {
res.type("application/json; charset=UTF-8");
List<Post> list = Post.findAll();
System.out.println("list is " + list);
return list;
}, new Gson()::toJson);
I'm looking for a way to read a MongoDB document into a POJO using GSON. It works just fine until you run into stuff like date's and longs.
I would like to write a custom adapter for Gson which will convert any BSON encoded long. Reading this post I have created my own adapter:
public class BsonLongTypeAdapter extends TypeAdapter<Long>
{
#Override
public void write(JsonWriter out, Long value) throws IOException
{
out.beginObject()
.name("$numberLong")
.value(value.toString())
.endObject();
}
#Override
public Long read(JsonReader in) throws IOException
{
in.beginObject();
assert "$numberLong".equals(in.nextName());
Long value = in.nextLong();
in.endObject();
return value;
}
}
I have defined the following tests to check if this works:
#Test
public void canWriteCorrectJSON() {
Gson gson = new GsonBuilder().registerTypeAdapter(Long.class, new BsonLongTypeAdapter()).create();
MyTestObject obj = new MyTestObject(1458569479431L);
String gsonString = gson.toJson(obj);
assertEquals("{\"timestamp\":{\"$numberLong\":\"1458569479431\"}}",gsonString);
}
#Test
public void canReadFromJSON() {
Gson gson = new GsonBuilder().registerTypeAdapter(Long.class, new BsonLongTypeAdapter()).create();
MyTestObject actualTaskObject = gson.fromJson("{\"timestamp\":{\"$numberLong\":\"1458569479431\"}}", MyTestObject.class);
MyTestObject taskObject = new MyTestObject(1458569479431L);
assertEquals(taskObject.getTimestamp(),actualTaskObject.getTimestamp());
}
private static class MyTestObject
{
long timestamp;
public MyTestObject(long ts)
{
timestamp = ts;
}
public long getTimestamp()
{
return timestamp;
}
public void setTimestamp(long timestamp)
{
this.timestamp = timestamp;
}
}
The first (write) test works just fine, but the read test fails on:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a long but was BEGIN_OBJECT at line 1 column 15 path $.timestamp
Because the read function from my adapter is never called. I presume this might be because I want to map to MyTestObject and not to Long, but I don't want to have to write adapters for all classes that contain longs.
Is it possible to write an adapter for GSON that converts all BSON longs I send into it?
I solved it using a CustomizedTypeAdapterFactory. See this question
Basically first write a customized adapter:
public abstract class CustomizedTypeAdapterFactory<C>
implements TypeAdapterFactory
{
private final Class<C> customizedClass;
public CustomizedTypeAdapterFactory(Class<C> customizedClass) {
this.customizedClass = customizedClass;
}
#SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return type.getRawType() == customizedClass
? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type)
: null;
}
private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type) {
final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<C>() {
#Override public void write(JsonWriter out, C value) throws IOException
{
JsonElement tree = delegate.toJsonTree(value);
beforeWrite(value, tree);
elementAdapter.write(out, tree);
}
#Override public C read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
afterRead(tree);
return delegate.fromJsonTree(tree);
}
};
}
/**
* Override this to muck with {#code toSerialize} before it is written to
* the outgoing JSON stream.
*/
protected void beforeWrite(C source, JsonElement toSerialize) {
}
/**
* Override this to muck with {#code deserialized} before it parsed into
* the application type.
*/
protected void afterRead(JsonElement deserialized) {
}
}
And then create a subclass for all classes that need to be taken into account. You do have to create one for every class containing a long (in this case). But you don't have to serialize anything but the long value (and any other bson specific values)
public class MyTestObjectTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyTestObject>
{
public MyTestObjectTypeAdapterFactory()
{
super(MyTestObject.class);
}
#Override
protected void beforeWrite(MyTestObject source, JsonElement toSerialize)
{
//you could convert back the other way here, I let mongo's document parser take care of that.
}
#Override
protected void afterRead(JsonElement deserialized)
{
JsonObject timestamp = deserialized.getAsJsonObject().get("timestamp").getAsJsonObject();
deserialized.getAsJsonObject().remove("timestamp");
deserialized.getAsJsonObject().add("timestamp",timestamp.get("$numberLong"));
}
}
and then generate Gson with:
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new MyTestObjectTypeAdapterFactory()).create();
I'm consuming a web service using Spring's RestTemplate and deserializing with Jackson.
In my JSON response from the server, one of the fields can be either an object or a list. meaning it can be either "result": [{}] or "result": {}.
Is there a way to handle this kind of things by annotations on the type I'm deserializing to ? define the member as an array[] or List<> and insert a single object in case of the second example ?
Can I write a new HttpMessageConverter that will handle it ?
Since you are using Jackson I think what you need is JsonDeserializer class (javadoc).
You can implement it like this:
public class ListOrObjectGenericJsonDeserializer<T> extends JsonDeserializer<List<T>> {
private final Class<T> cls;
public ListOrObjectGenericJsonDeserializer() {
final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
this.cls = (Class<T>) type.getActualTypeArguments()[0];
}
#Override
public List<T> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final ObjectCodec objectCodec = p.getCodec();
final JsonNode listOrObjectNode = objectCodec.readTree(p);
final List<T> result = new ArrayList<T>();
if (listOrObjectNode.isArray()) {
for (JsonNode node : listOrObjectNode) {
result.add(objectCodec.treeToValue(node, cls));
}
} else {
result.add(objectCodec.treeToValue(listOrObjectNode, cls));
}
return result;
}
}
...
public class ListOrObjectResultItemJsonDeserializer extends ListOrObjectGenericJsonDeserializer<ResultItem> {}
Next you need to annotate your POJO field. Let's say you have classes like Result and ResultItem:
public class Result {
// here you add your custom deserializer so jackson will be able to use it
#JsonDeserialize(using = ListOrObjectResultItemJsonDeserializer.class)
private List<ResultItem> result;
public void setResult(final List<ResultItem> result) {
this.result = result;
}
public List<ResultItem> getResult() {
return result;
}
}
...
public class ResultItem {
private String value;
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
}
Now you can check your deserializer:
// list of values
final String json1 = "{\"result\": [{\"value\": \"test\"}]}";
final Result result1 = new ObjectMapper().readValue(json1, Result.class);
// one value
final String json2 = "{\"result\": {\"value\": \"test\"}}";
final Result result2 = new ObjectMapper().readValue(json2, Result.class);
result1 and result2 contain the same value.
You can achieve what you want with a configuration flag in Jackson's ObjectMapper:
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
Just set this ObjectMapper instance to your RestTemplate as explained in this answer, and in the class you are deserializing to, always use a collection, i.e. a List:
public class Response {
private List<Result> result;
// getter and setter
}
I make a class extend AsyncTask to parse Json use Gson, because input can be many type of class I use generic type:
public class ApiResponse<T> extends AsyncTask...
I need know class type of T to pass Gson with:
(1)
Class<T> clazz =
(Class<T>)((ParameterizedType)
getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
new Gson().fromJson(reader, clazz);
However, Here T can be a class that have many type so that some time I pass class:
(2)
public class DataMessage<T> implements Serializable{...}
With these class have this format I received a Exception Caused by:
java.lang.ClassCastException: libcore.reflect.ParameterizedTypeImpl cannot be cast to java.lang.Class
at (1)
How do I do in this case ?
UPDATED:
public class DataMessage<T> implements Serializable{
#SerializedName("pagination")
private Pagination pagination;
#SerializedName("meta")
private Message meta;
#SerializedName("data")
private T data;
public DataMessage(T data) {
this.data = data;
}
public Pagination getPagination() {
return pagination;
}
public void setPagination(Pagination pagination) {
this.pagination = pagination;
}
public Message getMeta() {
return meta;
}
public void setMeta(Message meta) {
this.meta = meta;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public class Pagination {
#SerializedName("next_max_tag_id")
public String nextMaxTagId;
#SerializedName("deprecation_warning")
public String deprecationWarning;
#SerializedName("next_max_id")
public String nextMaxId;
#SerializedName("next_min_id")
public String nextMinId;
#SerializedName("min_tag_id")
public String minTagId;
#SerializedName("next_url")
public String nextUrl;
}
public class Message {
#SerializedName("error_type")
public String errorType;
#SerializedName("code")
public int code;
#SerializedName("error_message")
public String error_message;
}
}
When I need to parse JSON with specific class using Gson, I use GsonBuilder in this way
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try{
return new Date( json.getAsJsonPrimitive().getAsLong()*1000L );
}catch( Exception e ){
return null;
}
}
});
Gson gson = gsonBuilder.create();
In your case I think that you can simply parse your JSON in this way:
DataMessage<YourClass> dataMessage = gson.fromJson(yourJsonObj, DataMessage.class);
Another thing is that you can obtain the exact Type in this way
Type collectionType = new TypeToken<DataMessage<T>>() {}.getType();
But probably you will get a bad Json when you deserialize this...
The best way is to use the class instead of T so Gson know exactly what has to serialie/deserialize
Type collectionType = new TypeToken<DataMessage<YourClass>>() {}.getType();
I found this for you Deserializing Generic Types with GSON
Let's say you want to deserialize a list of DataMessage<T> using a JsonReader.
public <T> T Gson::fromJson(JsonReader reader, Type typeOfT)
Then you have
..
import java.lang.reflect.Type; // make sure you add this import
class DataMessageList<T>
{
public List<DataMessage<T>> list;
public DataMessageList ()
{
list = null;
}
public void readJson (JsonReader reader)
{
Type TT = new TypeToken<ArrayList< DataMessage<T> >>(){}.getType();
this.list = new Gson().fromJson(reader, TT);
}
}
Thanks to #Andrea Catania with your useful help,
Problem here is when I cast ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; to Class type, With DataMessage<T> type it can't cast.
But ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0] have type is Type so that I don't need cast it to Class, I retype clazz variable to Type (not Class) and problem have been resolved.
I'm using retrofit with gson to deserialize my json into realm objects. This works very well for the most part. Trouble arises when dealing with
RealmList(String(or any other basic data type))
Since Realm doesnt support RealmList where E doesnt extend Realm object, I wrapped String in a RealmObject.
public class RealmString extends RealmObject {
private String val;
public String getValue() {
return val;
}
public void setValue(String value) {
this.val = value;
}
}
My realm Object is as below
public class RealmPerson extends RealmObject {
#PrimaryKey
private String userId;
...
private RealmList<RealmString> stringStuff;
private RealmList<SimpleRealmObj> otherStuff;
<setters and getters>
}
SimpleRealmObj works fine as it only has String elements
public class SimpleRealmObj extends RealmObject {
private String foo;
private String bar;
...
}
How can I deserialize stringStuff? I tried using a gson TypeAdapter
public class RealmPersonAdapter extends TypeAdapter<RealmPerson> {
#Override
public void write(JsonWriter out, RealmPerson value) throws IOException {
out.beginObject();
Log.e("DBG " + value.getLastName(), "");
out.endObject();
}
#Override
public RealmPerson read(JsonReader in) throws IOException {
QLRealmPerson rList = new RealmPerson();
in.beginObject();
while (in.hasNext()) {
Log.e("DBG " + in.nextString(), "");
}
in.endObject();
return rList;
}
However I still hit the IllegalStateException
2334-2334/com.qualcomm.qlearn.app E//PersonService.java:71﹕ main com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was NAME at line 1 column 3 path $.
I tried RealmList, RealmString adapter earlier to no avail.
The only workaround I managed to find so far is https://github.com/realm/realm-java/issues/620#issuecomment-66640786
Any better options?
It is better to use JsonSerializer and JsonDeserializer rather than TypeAdapter for your RealmObject, because of 2 reasons:
They allow you to delegate (de)serialization for your RealmObject to the default Gson (de)serializer, which means you don't need to write the boilerplate yourself.
There's a weird bug in Gson 2.3.1 that might cause a StackOverflowError during deserialization (I tried the TypeAdapter approach myself and encountered this bug).
Here's how (replace Tag with your RealmObject class):
(NOTE that context.serialize and context.deserialize below are equivalent to gson.toJson and gson.fromJson, which means we don't need to parse the Tag class ourselves.)
Parser + serializer for RealmList<Tag>:
public class TagRealmListConverter implements JsonSerializer<RealmList<Tag>>,
JsonDeserializer<RealmList<Tag>> {
#Override
public JsonElement serialize(RealmList<Tag> src, Type typeOfSrc,
JsonSerializationContext context) {
JsonArray ja = new JsonArray();
for (Tag tag : src) {
ja.add(context.serialize(tag));
}
return ja;
}
#Override
public RealmList<Tag> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
RealmList<Tag> tags = new RealmList<>();
JsonArray ja = json.getAsJsonArray();
for (JsonElement je : ja) {
tags.add((Tag) context.deserialize(je, Tag.class));
}
return tags;
}
}
Tag class:
#RealmClass
public class Tag extends RealmObject {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Then register your converter class with Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapter(new TypeToken<RealmList<Tag>>() {}.getType(),
new TagRealmListConverter())
.create();
The error message "Expected a string but was NAME" can be solved by retrieving the name of the json object in the JsonReader before the actual json object (which is a String in your case).
You can take a look at the Android documentation for JsonReader. It has detailed explanation and code snippet. You can also take a look at the readMessage method in the sample code snippet in the documentation.
I have modified your read method to what I think it should be. NOTE: I didn't test the code, so there may be some minor errors in it.
#Override
public RealmPerson read(JsonReader in) throws IOException {
RealmPerson rList = new RealmPerson();
in.beginObject();
String name = "";
while (in.hasNext()) {
name = in.nextName();
if (name.equals("userId")) {
String userId = in.nextString();
// update rList here
} else if (name.equals("otherStuff")) {
// since otherStuff is a RealmList of RealmStrings,
// your json data would be an array
// You would need to loop through the array to retrieve
// the json objects
in.beginArray();
while (in.hasNext()) {
// begin each object in the array
in.beginObject();
name = in.nextName();
// the RealmString object has just one property called "value"
// (according to the code snippet in your question)
if (name.equals("val")) {
String val = in.nextString();
// update rList here
} else {
in.skipValue();
}
in.endObject();
}
in.endArray();
} else {
in.skipValue();
}
}
in.endObject();
return rList;
}
Let me know if this helps.
My gson typeAdapter was the culprit.
The above error was seen as I wasnt deserializing the json into RealmPerson correctly, the first field is not a String, hence
in.nextString()
was borking.
I looked at some example code and it hit me, I didnt have to use
in.beginObject() and in.endObject()
to deserialize a String. The below code works.
public class QLRealmStringAdapter extends TypeAdapter<QLRealmString> {
#Override
public void write(JsonWriter out, QLRealmString value) throws IOException {
Log.e("DBG " + value.getValue(), "");
out.value(value.getValue());
}
#Override
public RealmString read(JsonReader in) throws IOException {
RealmString rString = new RealmString();
if (in.hasNext()) {
String nextStr = in.nextString();
System.out.println("DBG " + nextStr);
rString.setValue(nextStr);
}
return rString;
}
}
Hope this helps someone.
i need a jackson serializer and deserializer for the Converting Arraylist to RealmList