How to serialize List object in Gson - java

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);

Related

Convert String to ArrayList<String> using GSON

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());

GSON: JSON deserialization to variable type (List/String)

At this point it's already an old question and I've probably read every related topic on SO.
But to the point. I need some advice or correction maybe?
For some reason we have generatable Jsons of 2 types:
{"data": {"id": "value"}} and {"data":[{"id": "value"}]}
Object and Array. There are also other params but they doesn't matter here. "id" is differ for every request. Sometimes it's userId, portfolioId etc. So I get "id" and pass it to related var.
For a long time I was working with the first case. And created POJO like this:
Data.class
public class Data {
#SerializedName("id")
#Expose
private String id;
public Data() {
}
public Data(String id) {
super();
this.id = id;
}
protected String getId() {
return id;
}
And I adress "data" paramets via User.class.
#JsonAdapter(UserDeserializer.class)
public Data data;
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
public User() {
}
public User(Data data) {
super();
this.data = data;
}
Gson gson = new Gson();
public String getPortfolioList(String tokenId, String userId) {
Call<User> call = apiRequest.getPortfolioList(userId, tokenId);
try {
User newResult = gson.fromJson(String.valueOf(call.execute().body()), User.class);
System.out.println(newResult.getData().getId());
} catch (IOException e) {
e.printStackTrace();
}
return getPortfolioId();
}
Deserializer.class
public class UserDeserializer implements JsonDeserializer<User> {
private Type listType = new TypeToken<List<Data>>(){}.getType();
#Override
public User deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
User user = new User();
JsonElement jsonElement;
if (json.isJsonArray()) {
jsonElement = json.getAsJsonArray();
user.data = context.deserialize(jsonElement,listType);
// user.data = new Gson().fromJson(jsonElement, new TypeToken<List<Data>>() {}.getType());
} else {
jsonElement = json.getAsJsonObject();
user.data = context.deserialize(jsonElement, Data.class);
// user.setData(new Gson().fromJson(jsonElement, new TypeToken<Data>() {}.getType()));
}
return user;
}
}
Gson builder in BaseApi class just in case:
Gson gson = new GsonBuilder().registerTypeAdapter(UserDeserializer.class, new UserDeserializer()).setLenient().create();
Without custom deserialization and Array JSON issue this would work perfectly. But now I have to determine "data" 's exact type I get.
In above case I get java.lang.ClassCastException: java.util.ArrayList cannot be cast to auto.Rest.Data
I assume I have to create another Data class (for example there will be DataObject & DataArray) and describe every parameter as I did before in Data.class to get this work? I think I do something wrong during deserialization but I'm not sure where tbh.
Or am I wrong and it is possible to invoke Data as List and Data as an Object of the same class?
I'm working on this for several days already(?) and was thinking about use generics instead of Gson help, yeah, I'm desperate. So any help appreciated.
if there is always one object, just add
json.getAsJsonArray().get(0);
public class UserDeserializer implements JsonDeserializer<User> {
private Type listType = new TypeToken<List<Data>>(){}.getType();
#Override
public User deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
User user = new User();
JsonElement jsonElement;
if (json.isJsonArray()) {
jsonElement = json.getAsJsonArray().get(0);
user.data = context.deserialize(jsonElement,listType);
// user.data = new Gson().fromJson(jsonElement, new TypeToken<List<Data>>() {}.getType());
} else {
jsonElement = json.getAsJsonObject();
user.data = context.deserialize(jsonElement, Data.class);
// user.setData(new Gson().fromJson(jsonElement, new TypeToken<Data>() {}.getType()));
}
return user;
}
}
if there are more objects, change field data to the list
public class UserDeserializer implements JsonDeserializer<User> {
private Type listType = new TypeToken<List<Data>>(){}.getType();
#Override
public User deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
User user = new User();
JsonElement jsonElement;
if (json.isJsonArray()) {
jsonElement = json.getAsJsonArray();
user.data = context.deserialize(jsonElement,listType);
// user.data = new Gson().fromJson(jsonElement, new TypeToken<List<Data>>() {}.getType());
} else {
jsonElement = json.getAsJsonObject();
List<Data> data = new ArrayList<Data>();
data.add(context.deserialize(jsonElement, Data.class)) ;
user.data = data ;
// user.setData(new Gson().fromJson(jsonElement, new TypeToken<Data>() {}.getType()));
}
return user;
}
}
and change User.class field data to List
public List<Data> data;
this is a similar topic in kotlin language link
If you always have object or one-element array you can write custom deserialiser as below:
class OneOrElementJsonDeserializer<T> implements JsonDeserializer<T> {
#Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json instanceof JsonArray) {
final JsonArray array = (JsonArray) json;
final int size = array.size();
if (size == 0) {
return null;
}
return context.deserialize(array.get(0), typeOfT);
}
return context.deserialize(json, typeOfT);
}
}
Your example model after simplification looks like below:
class User {
#JsonAdapter(OneOrElementJsonDeserializer.class)
private Data data;
public User() {
}
public User(Data data) {
super();
this.data = data;
}
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
#Override
public String toString() {
return "User{" +
"data=" + data +
'}';
}
}
class Data {
private String id;
protected String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Override
public String toString() {
return "Data{" +
"id='" + id + '\'' +
'}';
}
}
Example usage:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.JsonAdapter;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Type;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
User root = gson.fromJson(new FileReader(jsonFile), User.class);
System.out.println(root);
}
}
Above code for below JSON payload:
{
"data": [
{
"id": "c87ca3fe85781007869b83f"
}
]
}
prints:
User{data=Data{id='c87ca3fe85781007869b83f'}}
And for object case JSON payload:
{
"data": {
"id": "c87ca3fe85781007869b83f"
}
}
prints:
User{data=Data{id='c87ca3fe85781007869b83f'}}
In case your property could contain JSON object or multi-element array see my answer to this question Mapping Json Array to Java Models. There is implemented deserialiser which handle cases like this.

How to do conditional Gson deserialization default value

Imagine if I have the following JSON
{"game":"football", "people":"elevent"}
{"game":"badminton", "people":"two"}
My class as below
class Sport {
String game;
String people;
}
I could do a deserialize of my Json as below
Sport mySport = Gson().fromJson(json, Sport.class);
However, if my JSON is only
{"game":"football"}
{"game":"badminton"}
I would like it to automatically initialize people to "elevent" or "two", pending of the first field. Is there a way to configure my GsonBuilder() to have that achieve automatically during deserialization?
You could create a custom JsonDeserializer:
public class SportDeserializer implements JsonDeserializer<Sport> {
#Override
public Sport deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = (JsonObject) json;
String game = jsonObject.get("game").getAsString();
JsonElement nullablePeople = jsonObject.get("people");
String people = nullablePeople == null ? null : nullablePeople.getAsString();
if (people == null || people.isEmpty()) {
if (game.equals("football")) {
people = "elevent";
}
else if (game.equals("badminton")) {
people = "two";
}
}
Sport sport = new Sport();
sport.game = game;
sport.people = people;
return sport;
}
}
And then use the custom JsonDeserializer:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Sport.class, new SportDeserializer());
Gson gson = gsonBuilder.create();
Sport sport = gson.fromJson(jsonString, Sport.class);
My answer below isn't the best for this question since I simplified the question, and the other answer would suite this better.
But for more complicated scenarios, my answer below would help. It is basically setup a post-processing after the GSon converted.
I finally use Gson convert post-processing.
class Sport implements PostProcessingEnabler.PostProcessable {
String game;
String people;
#Override
public void gsonPostProcess() {
// The below is something simple.
// Could have more complicated scneario.
if (game.equals("football")) {
people = "elevant";
} else if (game.equals("badminton")) {
people = "two";
}
}
}
class PostProcessingEnabler implements TypeAdapterFactory {
public interface PostProcessable {
void gsonPostProcess();
}
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
T obj = delegate.read(in);
if (obj instanceof PostProcessable) {
((PostProcessable) obj).gsonPostProcess();
}
return obj;
}
};
}
}
Tribute goes to https://blog.simplypatrick.com/til/2016/2016-03-02-post-processing-GSON-deserialization/

Gson deserialize JSON array with multiple object types

I have some odd JSON like:
[
{
"type":"0",
"value":"my string"
},
{
"type":"1",
"value":42
},
{
"type":"2",
"value": {
}
}
]
Based on some field, the object in the array is a certain type.
Using Gson, my thought is to have a TypeAdapterFactory that sends delegate adapters for those certain types to a TypeAdapter, but I'm hung up on understanding a good way of reading that "type" field to know which type to create.
In the TypeAdapter,
Object read(JsonReader in) throws IOException {
String type = in.nextString();
switch (type) {
// delegate to creating certain types.
}
}
would assume the "type" field comes first in my JSON. Is there a decent way to remove that assumption?
Here is some code I wrote to handle an array of NewsFeedArticle and NewsFeedAd items in Json. Both items implement a marker interface NewsFeedItem to allow me to easily check if the TypeAdater should be used for a particular field.
public class NewsFeedItemTypeAdapterFactory implements TypeAdapterFactory {
#Override
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!NewsFeedItem.class.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedArticle.class));
TypeAdapter<NewsFeedAd> newsFeedAdAdapter = gson.getDelegateAdapter(this, TypeToken.get(NewsFeedAd.class));
return (TypeAdapter<T>) new NewsFeedItemTypeAdapter(jsonElementAdapter, newsFeedArticleAdapter, newsFeedAdAdapter).nullSafe();
}
private static class NewsFeedItemTypeAdapter extends TypeAdapter<NewsFeedItem> {
private final TypeAdapter<JsonElement> jsonElementAdapter;
private final TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter;
private final TypeAdapter<NewsFeedAd> newsFeedAdAdapter;
NewsFeedItemTypeAdapter(TypeAdapter<JsonElement> jsonElementAdapter,
TypeAdapter<NewsFeedArticle> newsFeedArticleAdapter,
TypeAdapter<NewsFeedAd> newsFeedAdAdapter) {
this.jsonElementAdapter = jsonElementAdapter;
this.newsFeedArticleAdapter = newsFeedArticleAdapter;
this.newsFeedAdAdapter = newsFeedAdAdapter;
}
#Override
public void write(JsonWriter out, NewsFeedItem value) throws IOException {
if (value.getClass().isAssignableFrom(NewsFeedArticle.class)) {
newsFeedArticleAdapter.write(out, (NewsFeedArticle) value);
} else if (value.getClass().isAssignableFrom(NewsFeedAd.class)) {
newsFeedAdAdapter.write(out, (NewsFeedAd) value);
}
}
#Override
public NewsFeedItem read(JsonReader in) throws IOException {
JsonObject objectJson = jsonElementAdapter.read(in).getAsJsonObject();
if (objectJson.has("Title")) {
return newsFeedArticleAdapter.fromJsonTree(objectJson);
} else if (objectJson.has("CampaignName")) {
return newsFeedAdAdapter.fromJsonTree(objectJson);
}
return null;
}
}
}
You can then register this with Gson using the following code.
return new GsonBuilder()
.registerTypeAdapterFactory(new NewsFeedItemTypeAdapterFactory())
.create();

Jackson Json deserialization of an object to a list

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
}

Categories

Resources