How do I write a custom JSON deserializer for Gson? - java

I have a Java class, User:
public class User
{
int id;
String name;
Timestamp updateDate;
}
And I receive a JSON list containing user objects from a webservice:
[{"id":1,"name":"Jonas","update_date":"1300962900226"},
{"id":5,"name":"Test","date_date":"1304782298024"}]
I have tried to write a custom deserializer:
#Override
public User deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
return new User(
json.getAsJsonPrimitive().getAsInt(),
json.getAsString(),
json.getAsInt(),
(Timestamp)context.deserialize(json.getAsJsonPrimitive(),
Timestamp.class));
}
But my deserializer doesn't work. How can I write a custom JSON deserializer for Gson?

I'd take a slightly different approach as follows, so as to minimize "manual" parsing in my code, as unnecessarily doing otherwise somewhat defeats the purpose of why I'd use an API like Gson in the first place.
// output:
// [User: id=1, name=Jonas, updateDate=2011-03-24 03:35:00.226]
// [User: id=5, name=Test, updateDate=2011-05-07 08:31:38.024]
// using java.sql.Timestamp
public class Foo
{
static String jsonInput =
"[" +
"{\"id\":1,\"name\":\"Jonas\",\"update_date\":\"1300962900226\"}," +
"{\"id\":5,\"name\":\"Test\",\"update_date\":\"1304782298024\"}" +
"]";
public static void main(String[] args)
{
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
gsonBuilder.registerTypeAdapter(Timestamp.class, new TimestampDeserializer());
Gson gson = gsonBuilder.create();
User[] users = gson.fromJson(jsonInput, User[].class);
for (User user : users)
{
System.out.println(user);
}
}
}
class User
{
int id;
String name;
Timestamp updateDate;
#Override
public String toString()
{
return String.format(
"[User: id=%1$d, name=%2$s, updateDate=%3$s]",
id, name, updateDate);
}
}
class TimestampDeserializer implements JsonDeserializer<Timestamp>
{
#Override
public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
long time = Long.parseLong(json.getAsString());
return new Timestamp(time);
}
}
(This assumes that "date_date" should be "update_date", in the original question.)

#Override
public User deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jobject = json.getAsJsonObject();
return new User(
jobject.get("id").getAsInt(),
jobject.get("name").getAsString(),
new Timestamp(jobject.get("update_date").getAsLong()));
}
I'm assuming User class has the appropriate constructor.

Today I was looking for this thing as my class had java.time.Instant and the default gson could not deserialize it. My POJOs look like this:
open class RewardResult(
#SerializedName("id")
var id: Int,
#SerializedName("title")
var title: String?,
#SerializedName("details")
var details: String?,
#SerializedName("image")
var image: String?,
#SerializedName("start_time")
var startTimeUtcZulu: Instant?, // Unit: Utc / Zulu. Unit is very important
#SerializedName("end_time")
var endTimeUtcZulu: Instant?,
#SerializedName("unlock_expiry")
var unlockExpiryTimeUtcZulu: Instant?,
#SerializedName("target")
var target: Int,
#SerializedName("reward")
var rewardItem: RewardItem
);
data class RewardItem(
#SerializedName("type")
var type: String?,
#SerializedName("item_id")
var itemId: Int,
#SerializedName("amount")
var amount: Int
)
Then for Instant variables, I parse the json's time variables and convert string to Instant. For integer , string, etc I use jsonObject.get("id").asInt etc. For other pojo, I use the default deserializer like this:
val rewardItem: RewardItem = context!!.deserialize(rewardJsonElement,
RewardItem::class.java);
So the corresponding custom deserializer looks like this:
val customDeserializer: JsonDeserializer<RewardResult> = object : JsonDeserializer<RewardResult> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): RewardResult {
val jsonObject: JsonObject = json!!.asJsonObject;
val startTimeString: String? = jsonObject.get("start_time")?.asString;
var startTimeUtcZulu: Instant? = createTimeInstant(startTimeString);
val endTimeString: String? = jsonObject.get("end_time")?.asString;
var endTimeUtcZulu: Instant? = createTimeInstant(endTimeString);
val unlockExpiryStr: String? = jsonObject.get("unlock_expiry")?.asString;
var unlockExpiryUtcZulu: Instant? = createTimeInstant(unlockExpiryStr);
val rewardJsonElement: JsonElement = jsonObject.get("reward");
val rewardItem: ridmik.one.modal.reward.RewardItem = context!!.deserialize(rewardJsonElement,
ridmik.one.modal.reward.RewardItem::class.java); // I suppose this line means use the default jsonDeserializer
var output: ridmik.one.modal.reward.RewardResult = ridmik.one.modal.reward.RewardResult(
id = jsonObject.get("id").asInt,
title = jsonObject.get("title")?.asString,
details = jsonObject.get("details")?.asString,
image = jsonObject.get("image")?.asString,
startTimeUtcZulu = startTimeUtcZulu,
endTimeUtcZulu = endTimeUtcZulu,
unlockExpiryTimeUtcZulu = unlockExpiryUtcZulu,
target = jsonObject.get("target").asInt,
rewardItem = rewardItem
);
Timber.tag(TAG).e("output = "+output);
return output;
}
}
Finally, I create my custom gson like this:
val gsonBuilder = GsonBuilder();
gsonBuilder.registerTypeAdapter(RewardResult::class.javaObjectType,
this.customJsonDeserializer);
val customGson: Gson = gsonBuilder.create();

Related

Different typeconverters for different custom object entity in room database

Here i need to store multiple custom entities in same table in room database. This things are working in single typeconverter but when it comes in the case of multiple typeconverters then it is throwing error.
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
#Entity(tableName = "tbl_finalOrder")
#TypeConverters(ItemEntityConverter::class)
data class FinalOrdersEntity (
#PrimaryKey(autoGenerate = true)
val id:Int,
val itemEntity: ItemEntity?,
// val itemEntity: String? = null,
var quantity: String?=null,
var unitName: String?=null,
var unitId: Int?=null,
var statusOfScheme: Boolean?=null,
#TypeConverters(SchemeDetailConverter::class)
var listOfScheme: List<SchemeDetailEntity>?=null,
val child_items: String?=null,
val selectedOffer:String? = null,
var offer:String?=null
)
class ItemEntityConverter {
#TypeConverter
fun stringToItemEntity(string: String?): ItemEntity = Gson().fromJson(string,ItemEntity::class.java)
#TypeConverter
fun itemEntityToString(list: ItemEntity?): String =Gson().toJson(list)
}
class SchemeDetailConverter {
#TypeConverter
fun stringToSchemeDetailEntity(json: String?): List<SchemeDetailEntity> {
val gson = Gson()
val type: Type = object : TypeToken<List<SchemeDetailEntity?>?>() {}.type
return gson.fromJson<List<SchemeDetailEntity>>(json, type)
}
#TypeConverter
fun schemeDetailEntityToString(list: List<SchemeDetailEntity?>?): String {
val gson = Gson()
val type: Type = object : TypeToken<List<SchemeDetailEntity?>?>() {}.type
return gson.toJson(list, type)
}
}
When i run this then it ask to make typeconverter class for listOfScheme.However, there is one already for it.
error
FinalOrdersEntity.java:22: error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private java.util.List<com.myproject.database.entities.SchemeDetailEntity> listOfScheme;
Can you tell me where and what i am missing here. Thank you in advance..
data class FinalOrdersEntity (
#PrimaryKey(autoGenerate = true)
val id:Int,
#TypeConverters(ItemEntityConverter::class)
val itemEntity: ItemEntity?,
// val itemEntity: String? = null,
var quantity: String?=null,
var unitName: String?=null,
var unitId: Int?=null,
var statusOfScheme: Boolean?=null,
#TypeConverters(SchemeDetailConverter::class)
var listOfScheme: List<SchemeDetailEntity>?=null,
val child_items: String?=null,
val selectedOffer:String? = null,
var offer:String?=null
)
Check if the SchemeDetailEntity has any variable with class as the type,if yes then you should add the type converter class for that too

GSON deserialize two different object type on same list

I'm trying to consume an API that have a return like:
{
"data":{
"id":"12345",
"name":"Some Name",
"regions":[
"r987",
"r654"
]
}
}
that the region attribute is an List<String> with only the regions ID.
But I can ask to the same request retrieve the full region object, so the json change to:
{
"data":{
"id":"12345",
"name":"Some Name",
"regions":{
"data":[
{
"id":"r987",
"name":"region 1"
},
{
"id":"r654",
"name":"region 2"
}
]
}
}
}
I thought of creating an adapter to always translate the list of Id to return a list with the full object, like
class RegionListAdapter implements JsonDeserializer<List<Region>> {
#Override
public List<Region> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext jsc) throws JsonParseException {
List<Region> result = new ArrayList<>();
if (json.isJsonObject()) {
JsonObject envelope = json.getAsJsonObject();
JsonArray data = envelope.getAsJsonArray("data");
result = jsc.deserialize(data, typeOfT);
} else {
JsonArray idArray = json.getAsJsonArray();
for(JsonElement e : idArray){
String regionId = e.getAsString();
Region region = new Region(regionId);
result.add(region);
}
}
return result;
}
}
and with this everything work, receiving only the ID or the full object...
The thing is, I have others attributes that work on the same way. Is there any way to make this adapter work generically or do it differently? Or I need to create a Adapter for each object?
The hard part of making this generic is that you need to create new instances of the object type that is in the list. If all the objects are going to use the field named id as what is going in the list version, we can use that to create a new instance. The easiest hack is shown below, where we stuff the string as the id field into a new JsonObject and use Gson to construct the new object.
class IdListAdapter<T> implements JsonDeserializer<List<T>> {
#Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext jsc) throws JsonParseException {
if (json.isJsonObject()) {
JsonObject envelope = json.getAsJsonObject();
JsonArray data = envelope.getAsJsonArray("data");
return jsc.deserialize(data, typeOfT);
} else {
JsonArray idArray = json.getAsJsonArray();
List<T> result = new ArrayList<T>(idArray.size());
for (JsonElement id : idArray) {
if (typeOfT instanceof ParameterizedType) {
Type parameterType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
JsonObject obj = new JsonObject();
obj.add("id", id);
T element = jsc.deserialize(obj, parameterType);
result.add(element);
}
}
return result;
}
}
}

Gson Postgres Timestamp serialization

the problem is in the Postegres timestamp serialization using GSON,
private static final GsonBuilder GSON_BASE = Converters
.registerAll(new GsonBuilder().disableHtmlEscaping())
.registerTypeAdapter(InfoTransfer.class, new InfoTransfer.Adapter())
.setDateFormat(DateFormat.FULL, DateFormat.FULL) //Line added but it seems work for MySQL DB Timestamp
.setPrettyPrinting()
.create();
.
//inner class in InfoTransfer
public static class Adapter implements JsonSerializer<InfoTransfer>{
#Override
public JsonElement serialize(InfoTransfer src, Type typeOfSrc, JsonSerializationContext context) {
Gson gson= new Gson();
JsonObject jsonObject= (JsonObject) gson.toJsonTree(src);
return new JsonPrimitive(jsonObject.getAsString());
}
}
.
.
.
Log.d("Result",GSON_BASE.toJson(data));
expected result :
"created_at": "2016-10-13 18:18:51.64208+01"
result :
\"created_at\": \"\\u0000\\u0001\\ufffdM\\u0015q\\ufffd}\"
so any suggestion ?
The date format can be set as follows:-
setDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSX")
Example:-
public static void main(String[] args) {
String jsonString = "{\"customorId\":\"506\",\"joiningDate\":\"2016-10-26 19:49:17.290671+01\"}";
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSX")
.setPrettyPrinting()
.create();
//Deserialize
Customer customer = gson.fromJson(jsonString, Customer.class);
System.out.println(customer.toString());
//Serialize
Customer customerSerialize = new Customer();
customerSerialize.setCustomorId("123");
customerSerialize.setJoiningDate(new Date());
System.out.println(gson.toJson(customerSerialize));
}
Customer class:-
public class Customer implements Serializable {
private static final long serialVersionUID = 1100012615187080642L;
private String customorId;
private Date joiningDate;
}
Sample output:-
Customer [customorId=506, joiningDate=Wed Oct 26 19:54:07 BST 2016]
{
"customorId": "123",
"joiningDate": "2016-10-26 20:23:37.000811+01"
}

json into list with Gson

I have a Json like below:
{
"searchResults": {
"searchCriteria": {
"location": {
"originalLocation": null
},
"startAndEndDate": {
"start": "2016-10-06T00:00:00",
"end": "2016-10-09T00:00:00"
},
"solution": [
{
"resultID": "O1MDc1MD",
"selected": false,
"charges": {
"localCurrencyCode": "USD",
"averagePricePerNight": 153
},
"starRating": 3.5
},
{
"resultID": "0MDc1MD",
"selected": false,
"charges": {
"localCurrencyCode": "USD",
"averagePricePerNight": 153
},
"starRating": 3.5
}
....
I have class with attributes starRating and averagePricePerNight which essentially formulates into my POJO.
class ResponseModel {
Int starRating; Int averagePricePerNight
}
I want to parse this JSON and return a List containing :
List(ResponseModel(3.5,900), ResponseModel(3.5,100), ResponseModel(4.5,1000))
I tried to get the json as a List but then i am unable to find examples to get two elements from JSon.
You can write a custom deserializer:
class Deserializers {
public static ResponseModel responseModelDeserializer(JsonElement json, Type typeOfT,
JsonDeserializationContext context) {
JsonObject obj1 = json.getAsJsonObject();
JsonObject obj2 = obj1.get("charges").getAsJsonObject();
double starRating = obj1.get("starRating").getAsDouble();
int averagePricePerNight = obj2.get("averagePricePerNight").getAsInt();
return new ResponseModel(starRating, averagePricePerNight);
}
}
Register it when building Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ResponseModel.class,
(JsonDeserializer<ResponseModel>) Deserializers::responseModelDeserializer
// ^^^ Cast is needed because the parameter has type Object
)
.create();
(Other options include, besides a method reference, are; lambda, anonymous class, or just a regular class. But this one is my favourite.)
Parse your json:
// Get root json object
JsonObject root = new JsonParser().parse(input).getAsJsonObject();
Type tt = new TypeToken<List<ResponseModel>>() {}.getType();
// Get array
List<ResponseModel> mo = gson.fromJson(root.get("solution"), tt);
System.out.println(mo); // [3.5 : 153, 3.5 : 153]
Where ResponseModel is:
class ResponseModel {
private final double starRating;
private final int averagePricePerNight;
public ResponseModel(double starRating, int averagePricePerNight) {
this.starRating = starRating;
this.averagePricePerNight = averagePricePerNight;
}
#Override
public String toString() {
return String.format("%s : %s", starRating, averagePricePerNight);
}
}
I made starRating a double since it seems to be one in your example.

Jackson double serialized json string to Java object

I have a Json string which has a string message field.
String:
{ "Type" : "Text",
"Subject" : "data received",
"Message" :"{\\"language\\":\\"US\\",\\"data\\":\\"signature\\"}"
}
I want to convert it into the following structure:
Notification.java
public class Notification {
String type;
String subject;
Message message;
}
Message.java
public class Message {
String language;
String data;
}
Is there a way in which I can directly convert the string to a Java object of the above structure? I want to avoid deserializing twice.
You can create a custom Deserializer to deserialize the Message text into Message object and annotate the Message class with #JsonDeserialize:
#JsonDeserialize(using = MessageDeserializer.class)
public class Message {
String language;
String data;
}
public class MessageDeserializer extends JsonDeserializer<Message> {
public MessageDeserializer() {
super();
}
#Override
public Message deserialize(
final JsonParser jsonParser, final DeserializationContext deserializationContext) throws
IOException, JsonProcessingException {
final String messageText = jsonParser.getText();
// parse messageText into Message object
}
}
I am not sure my solution is acceptable since it does require additional explicit call to ObjectMapper to perform deserialization of the string value of Message.
However, this is it is done during the buildup of Notification object and does not require a String message property.
You need to add a ctor with String argument to Message class, where you can deserialize the String into Map and extract the instance propertieds:
public Message(String str) {
try {
#SuppressWarnings("unchecked")
Map<String, Object> map =
(Map<String, Object>)new ObjectMapper().readValue(str, Map.class);
language = map.containsKey("language") ? map.get("language").toString() : null ;
data = map.containsKey("data") ? map.get("data").toString() : null ;
} catch (Exception e) {
e.printStackTrace();
}
}
the new ctor will be called by Jackson when you deserialize a Notification object:
Notification n = (Notification)new ObjectMapper().readValue(reader, Notification.class);
You can convert json string into key-value pairs in Map.You will have to do twice as the Message value is again a json string.Use org.json for JSONObject
Map<String, String> map = new HashMap<String, String>();
JSONObject j = new JSONObject(str);
Iterator<String> keys = j.keys();
while( keys.hasNext() ){
String key = (String)keys.next();
String val = j.getString(key);
map.put(key, val);}
Then retrieve the values by iterating over the keys and pass the values into the class constructor
Then map.get(key) can be used to retrieve the values and will be passed into constructors of the classes.
The org.json library is easy to use:
//Create Json object to parse string
// str is input string
JSONObject obj = new JSONObject(str);
//Create Message
Message mess = new Message();
JSONObject obj2 = new JSONObject(obj.getString("Message"));
mess.data = obj2.getString("data");
mess.language = obj2.getString("language");
//Create Notification
Notification noti = new Notification();
noti.message = mess;
noti.subject = obj.getString("Subject");
noti.type = obj.getString("Type");

Categories