GSON custom Deserializer throws error that doesn't make sense - java

I get following exception.
Caused by: java.lang.ClassCastException: Cannot cast java.util.LinkedList to com.example.weltliste.Task
at this line: tasks.add(g.fromJson(in, Task.class));
Here is the full method + my custom deserializer
public class FileHandler {
private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
Gson g = new GsonBuilder().setPrettyPrinting()
.registerTypeAdapter(Task.class, new TaskSerializer())
.registerTypeAdapter(Task.class, new TaskDeserializer()).create();
public List<Task> read(InputStream stream) throws IOException {
InputStreamReader in = new InputStreamReader(stream);
List<Task> tasks = null;
tasks.add(g.fromJson(in, Task.class));
return tasks;
}
Here is my custom deserializer
public class TaskDeserializer implements JsonDeserializer<List<Task>> {
#Override
public List<Task> deserialize(JsonElement json,
Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<Task> tasks = new LinkedList<>();
JsonArray jsonArray = json.getAsJsonArray();
for (int i = 0; i < jsonArray.size(); i++) {
JsonObject jsonObject;
jsonObject = jsonArray.get(i).getAsJsonObject();
String title = jsonObject.get("title").getAsString();
String value = jsonObject.get("value").getAsString();
LocalDateTime date = LocalDateTime.parse(jsonObject.get("date").getAsString());
boolean checked = jsonObject.get("checked").getAsBoolean();
tasks.add(new Task(title, value, date, checked));
}
return tasks;
}
}

Related

how to get field name of class in java

Hello all sorry my language is Bad!
This is my code:
MyCustomClass temp = new MyCustomClass();
for (int i = 0; i < jsonarray.length(); i++) {
JSONObject obj = jsonarray.getJSONObject(i);
temp.ID = obj.getInt("ID");
temp.PicName = obj.getString("PicName");
temp.PicURL = obj.getString("PicURL");
Items.add(temp);
}
I would like to take this dynamic
Something like this
MyCustomClass temp = new MyCustomClass();
Field[] myFields= MyCustomClass.class.getFields();
for (int i = 0; i < jsonarray.length(); i++) {
JSONObject obj = jsonarray.getJSONObject(i);
for(int j=0;j<myFields.lenghth();j++)
{
myFields[j]=obj.getString(myFields[j].toString());
Items.add(temp);
}
}
How to do it?
*Name of jason fields = Name of MycustomClass Fields
Jackson and Gson will do all this for you.
static class TestClass {
public int id;
public String name;
}
#Test
public void gson() {
Gson gson = new Gson();
TestClass[] item = gson.fromJson("[{'id': 1, 'name': 'testclass'}]", TestClass[].class);
assertThat(item[0].id, is(1));
assertThat(item[0].name, is("testclass"));
assertThat(item.length, is(1));
}
#Test
public void jackson() throws IOException {
ObjectMapper jacksonObjectMapepr = new ObjectMapper();
TestClass[] item = jacksonObjectMapepr.readValue("[{\"id\": 1, \"name\": \"testclass\"}]", TestClass[].class);
assertThat(item[0].id, is(1));
assertThat(item[0].name, is("testclass"));
assertThat(item.length, is(1));
}
However to answer your question, you can look up what each field with getDeclaredField. But you will have to do quite some work to handle all the type mapping.
#Test
public void sillyWayIDontRecommend() throws NoSuchFieldException, IllegalAccessException {
TestClass[] item = new TestClass[1];
JsonArray array = new JsonParser().parse("[{\"id\": 1, \"name\": \"testclass\"}]").getAsJsonArray();
for(int i = 0; i<array.size(); i++) {
item[i] = new TestClass();
JsonObject object = array.get(i).getAsJsonObject();
for(Map.Entry<String, JsonElement> entry : object.entrySet()) {
Field field = TestClass.class.getDeclaredField(entry.getKey());
if(field.getType().equals(int.class)) {
field.setInt(item[i], entry.getValue().getAsInt());
} else {
field.set(item[i], entry.getValue().getAsString());
}
}
}
assertThat(item[0].id, is(1));
assertThat(item[0].name, is("testclass"));
assertThat(item.length, is(1));
}
With jackson library you are able to set up your Pojos directly with json annotations and you are able to convert your JSON strings directly to java objects.
A generic way for parsing can be something like that:
public static <T> T deserialize(T t, Class<T> clazz, String json) throws JsonParseException, JsonMappingException, IOException{
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, clazz);
}
T - is your object and return type
clazz - is your Pojo
json - is your json String
You can call the method like this:
MyCustomClass myCustomClass= new MyCustomClass();
myCustomClass= JsonUtil.deserialize(myCustomClass, MyCustomClass.class, json);
Your Pojo can look like this:
#JsonIgnoreProperties // ignores properties from json String which are not in your Pojo
public class MyCustomClass {
#JsonProperty("anotherNameIfFieldNameIsNotEqual")
private String picName;
private String picURL;
public String getPicName() {
return picName;
}
public void setPicName(String picName) {
this.picName = picName;
}
public String getPicURL() {
return picURL;
}
public void setPicURL(String picURL) {
this.picURL= picURL;
}
}
And this is the maven dependency you need:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
Documentation and Example
You can get all class field with this construction:
Class class = ...//obtain class object
Field[] methods = class.getFields();
With your class it's:
MyCustomClass temp = new MyCustomClass();
Field[] methods = temp.getFields();

Gson time deserilization

I have a JSON string that I want to deserilizie using Gson - {"Id":3,"Title":"Roskilde","Description":"Famous Danske festival","StartingTime":"2016-06-12T00:00:00","Duration":"02:02:00"}
The error I get when I try to deserilize the Duration field:
Unparseable date: "02:02:00"
The deserilizer(my idea was to add two possible formats of date deserialization):
Gson gSon= new GsonBuilder().registerTypeAdapter(Date.class, new DateDeserializer()).create();
private static final String[] DATE_FORMATS = new String[] {
"yyyy-MM-dd'T'HH:mm:ss",
"HH:mm:ss"
};
private class DateDeserializer implements JsonDeserializer<Date> {
#Override
public Date deserialize(JsonElement jsonElement, Type typeOF,
JsonDeserializationContext context) throws JsonParseException {
for (String format : DATE_FORMATS) {
try {
return new SimpleDateFormat(format, Locale.US).parse(jsonElement.getAsString());
} catch (ParseException e) {
}
}
throw new JsonParseException("Unparseable date: \"" + jsonElement.getAsString()
+ "\". Supported formats: " + Arrays.toString(DATE_FORMATS));
}
}
and my Event Class(as you can see "Duration" is not of a type date - it's of the type Time - what should I do to make the deserilizer read Duration as type time not date?
private int Id;
private String Title;
private String Description;
private Date StartingTime;
private Time Duration;
public Event(int id, String title,String description, String place, Date startingTime, Time duration)
{
this.Id = id;
this.Description = description;
this.Title = title;
this.StartingTime = startingTime;
this.Duration = duration;
}
Add another class for Time deserialization - make sure you convert the return value to "Time".
(Also see: cast a String to sql time)
private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
private static final String TIME_FORMAT = "HH:mm:ss";
private class DateDeserializer implements JsonDeserializer<Date> {
#Override
public Date deserialize(JsonElement jsonElement, Type typeOF,
JsonDeserializationContext context) throws JsonParseException {
try {
return new SimpleDateFormat(DATE_FORMAT, Locale.US).parse(jsonElement.getAsString());
} catch (ParseException e) {
}
throw new JsonParseException("Unparseable date: \"" + jsonElement.getAsString()
+ "\". Supported formats: " + DATE_FORMAT);
}
}
private class TimeDeserializer implements JsonDeserializer<Time> {
#Override
public Time deserialize(JsonElement jsonElement, Type typeOF,
JsonDeserializationContext context) throws JsonParseException {
try {
String s = jsonElement.getAsString();
SimpleDateFormat sdf = new SimpleDateFormat(TIME_FORMAT, Locale.US);
sdf.parse(s);
long ms = sdf.parse(s).getTime();
Time t = new Time(ms);
return t;
} catch (ParseException e) {
}
throw new JsonParseException("Unparseable time: \"" + jsonElement.getAsString()
+ "\". Supported formats: " + TIME_FORMAT);
}
}
Register both classes:
GsonBuilder gSonBuilder= new GsonBuilder();
gSonBuilder.registerTypeAdapter(Date.class, new DateDeserializer());
gSonBuilder.registerTypeAdapter(Time.class, new TimeDeserializer());
Gson gSon = gSonBuilder.create();
You should register a deserializer for both Date and Time. Something like this:
try{
// Convert JSON-string to a List of Event objects
Type listType = new TypeToken<ArrayList<Event>>(){}.getType();
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
#Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try{
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
return df.parse(json.getAsString());
}
catch(ParseException ex){
return null;
}
});
gsonBuilder.RegisterTypeAdapter(Time.class, new JsonDeserializer<Time>(){
#Override
public Time deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try{
DateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
return df.parse(json.getAsString());
}
catch(ParseException ex){}
}
return null;
}
});
Gson eventGson = gsonBuilder.create();
events = dateGson.fromJson(json, listType);
}
catch(JsonParseException ex){
ex.printStackTrace();
}
PS: This code is untested, since I didn't had a lot of time. But I think you get the general idea. You just need two JsonDeserializers, one for time and one for Date.

xStream changes order of objects in write/readObject with JSON serialization

We use XStream to serialize objects to JSON and vice versa.
We init xStream like this
XStream xStream = new XStream(new JettisonMappedXmlDriver(new Configuration(), false));
xStream.ignoreUnknownElements();
xStream.setMode(XStream.XPATH_RELATIVE_REFERENCES);
We have test class
public static class TestWOWithBI implements Serializable{
private static final long serialVersionUID = -4720678317857471031L;
private transient String customerNickname;
private transient String customerUuid;
private transient BigInteger discussionId;
private transient String message;
public TestWOWithBI(String customerNickname, String customerUuid, BigInteger discussionId, String message){
this.customerNickname = customerNickname;
this.customerUuid = customerUuid;
this.discussionId = discussionId;
this.message = message;
}
private final void writeObject(final ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(customerNickname);
out.writeObject(customerUuid);
out.writeObject(discussionId);
out.writeObject(message);
}
private final void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
customerNickname = (String) in.readObject();
customerUuid = (String) in.readObject();
discussionId = (BigInteger) in.readObject();
message = (String) in.readObject();
}
}
After serialization it looks like this:
{
"somethere.ObjectToJSONSerializerTest$TestWOWithBI": {
"#serialization": "custom",
"somethere.ObjectToJSONSerializerTest$TestWOWithBI": {
"default": "",
"string": ["name",
"uuid",
"message"],
"big-int": 1
}
}
}
and deserialization fails with class cast. It was on 1.3.1 and 1.4.7 versions. Looks like bug to me, but may be where is some settings?
UPD:
Seems like org.codehaus.jettison.mapped.MappedXMLStreamWriter.JSONPropertyObject#withProperty
if(old != null) {
JSONArray values;
// Convert an existing property to an array
// and append to the array
if (old instanceof JSONArray) {
values = (JSONArray)old;
} else {
values = new JSONArray();
values.put(old);
}
values.put(value);
object.put(property.getKey(), values);
} else if(getSerializedAsArrays().contains(property.getKey())) {
JSONArray values = new JSONArray();
values.put(value);
object.put(property.getKey(), values);
} else {
// Add the property directly.
object.put(property.getKey(), value);
}
It just group elements of same type.

Json jackson : can't parse a json a file

I m trying to parse a JSON file and store it in an list. I m getting this error :
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
Here is my JSON file
{ "budgetList":[
{
"label":"Salary Tim",
"category":"Monthly Income",
"real":1590,
"estimated":1590,
"date":"",
"month":"",
"year":"",
"type":"Income"
},
{
"label":"Salary Tom",
"category":"Monthly Income",
"real":1540,
"estimated":1540,
"date":"",
"month":"",
"year":"",
"type":"Income"
}
]
}
Here is my code
Budget :
public class Budget {
private String label;
private String category;
private int real;
private int estimated;
private Date date;
private int year;
private String type;
....
....
}
My service :
List<Budget> budgets = objectMapper.readValue(new File("src/main/resources/json/new_exercise.json"), TypeFactory.defaultInstance().constructCollectionType(List.class,
Budget.class));
Where am I wrong?
Thanks in advance.
ANSWER FOUND
Code is
ObjectMapper objectMapper = new ObjectMapper();
List<Budget> budgets = null;
JsonNode node = objectMapper.readTree(new File("src/main/resources/json/new_exercise.json"));
node = node.get("budgetList");
TypeReference<List<Budget>> typeRef = new TypeReference<List<Budget>>(){};
budgets = objectMapper.readValue(node.traverse(), typeRef);
can you, use GSON library? Is very simple
Reader reader = new InputStreamReader(new
FileInputStream("/opt/file.json"));
Gson gson = gsonBuilder.create();
List listBudget = gson.fromJson(reader, new
TypeToken>() {}.getType());
I think that the only problem is when Date and integer parser when is empty. but you can register adapters like:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public Date deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
try {
return df.parse(json.getAsString());
} catch (ParseException e) {
return null;
}
}
});
Reader reader = new InputStreamReader(new
FileInputStream("/opt/file.json"));
Gson gson = gsonBuilder.create();
List listBudget = gson.fromJson(reader, new
TypeToken>() {}.getType());
It works for you?, and dont forget to validate you json. "

GSON failing to deserialize object it had previously serialized with: Expected BEGIN_OBJECT but was STRING

This is a narrow-down example and so nonsensical but demonstrates the problem.
The following code:
import java.util.*;
import com.google.gson.*;
class X {}
class SomeType {
private Map <X, String> map;
public SomeType() {
this.map = new HashMap<X, String>();
map.put(new X(), "b");
}
}
public class FooMain {
private static Gson gson = new GsonBuilder().serializeNulls().create();
public static void main(String args[]) throws Exception {
String foo = gson.toJson(new SomeType(), SomeType.class);
System.out.println(foo); // line 20
SomeType st = gson.fromJson(foo, SomeType.class); // line 21
}
}
fails with:
[java] Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 20
[java] at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:176)
[java] at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40)
[java] at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:187)
[java] at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:146)
[java] at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
[java] at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
[java] at com.google.gson.Gson.fromJson(Gson.java:795)
[java] at com.google.gson.Gson.fromJson(Gson.java:761)
[java] at com.google.gson.Gson.fromJson(Gson.java:710)
[java] at com.google.gson.Gson.fromJson(Gson.java:682)
[java] at FooMain.main(FooMain.java:21)
[java] Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 20
[java] at com.google.gson.stream.JsonReader.expect(JsonReader.java:339)
[java] at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:322)
[java] at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:165)
[java] ... 10 more
whereas line 20 prints:
{"map":{"X#185b10b":"b"}}
Gson serializes a Map by using the key as the JSON key and the map element's value as the value. Because your X class doesn't have a custom overriden toString() method, it uses Object#toString() and serializes it as X#185b10b but can't deserialize it. It actually wouldn't be able to deserialize it even if you did provide a toString().
I guess you've found an edge case where you cannot serialize everything correctly. The key of a JSON object must be a String.
Ok, so thanks to Sotirios Delimanolis answer I found the following:
you can provide a custom deserializer only for the Map key class. In that case the toString method of that key class has to provide the "serialization" format
or
you can provide a generic adapter (serializer + deserializer) for the Map class itself, plus a generic adapter (serializer + deserializer) for the Map key class. The only advantage of this method is that the toString method of the key class can then be used for other purposes and doesn't have to play along with the deserializer.
Following is the complete code for the second case:
import java.util.*;
import com.google.gson.*;
import java.lang.reflect.Type;
import org.apache.commons.lang3.StringUtils;
class X {
public int x;
public X(int x) {
this.x = x;
}
public String toString() {
return String.format("boo ha ha %d", x);
}
}
class SomeType {
private Map <X, String> map;
public SomeType() {
this.map = new HashMap<X, String>();
map.put(new X(2), "b");
}
public String toString() {
List<String> rv = new ArrayList<>();
for (X x : map.keySet())
rv.add(String.format("%s -> %s\n", x.toString(), map.get(x)));
return StringUtils.join(rv, "\n");
}
}
public class FooMain {
public static void main(String args[]) throws Exception {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(X.class, new XAdapter());
gsonBuilder.registerTypeAdapter(Map.class, new MapAdapter());
Gson gson = gsonBuilder.serializeNulls().create();
SomeType original = new SomeType();
System.out.println("original is: "+original);
String foo = gson.toJson(original, SomeType.class);
System.out.println("JSON form is: "+foo);
SomeType reconstructed = gson.fromJson(foo, SomeType.class);
System.out.println("reconstructed is: "+reconstructed);
}
}
class MapAdapter implements JsonSerializer<Map<?, ?>>, JsonDeserializer<Map<?, ?>> {
#Override
public JsonElement serialize(Map<?, ?> m, Type typeOfT, JsonSerializationContext context) {
JsonArray rv = new JsonArray();
for (Object k : m.keySet()) {
JsonObject kv = new JsonObject();
kv.add ("k" , context.serialize(k));
kv.addProperty("ktype" , k.getClass().getName());
kv.add ("v" , context.serialize(m.get(k)));
kv.addProperty("vtype" , m.get(k).getClass().getName());
rv.add(kv);
}
return rv;
}
#Override
public Map<?, ?> deserialize(JsonElement _json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonArray json = (JsonArray) _json;
Map<Object, Object> rv = new HashMap<>();
for (int i = 0 ; i < json.size() ; i++) {
JsonObject o = (JsonObject) json.get(i);
String ktype = o.getAsJsonPrimitive("ktype").getAsString();
String vtype = o.getAsJsonPrimitive("vtype").getAsString();
Class<?> kklass = null;
Class<?> vklass = null;
try {
kklass = Class.forName(ktype);
vklass = Class.forName(vtype);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new JsonParseException(e.getMessage());
}
Object k = context.deserialize( o.get("k"), kklass);
Object v = context.deserialize( o.get("v"), vklass);
rv.put(k, v);
}
return rv;
}
}
class XAdapter implements JsonSerializer<X>, JsonDeserializer<X> {
#Override
public JsonElement serialize(X x, Type typeOfT, JsonSerializationContext context) {
return context.serialize(x.x);
}
#Override
public X deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String s = json.getAsString();
int x = Integer.valueOf(s);
return new X(x);
}
}

Categories