Gson: Unable to parse date - java

My json input looks like:
{ user: "sample-user", date : 1225864800 }
And my DateDeserializer class is:
private class DateDeserializer implements JsonDeserializer<Date>
{
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
System.out.println("Deserializer...");
return new Date(json.getAsJsonPrimitive().getAsLong());
}
}
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new DateDeserializer());
gson.fromJson(new FileReader("input.json"), MyType.class);
Even after setting up everything, I'm getting
java.text.ParseException: Unparseable Date: "1225864800"
Am I doing anything wrong here? Please help.

You're trying to deserialize the entire Json structure as a Date, not just the date field. You'd need something like:
User user = jsonElement.getAsJsonObject().get( "user" ).getAsString();
Date date = new Date(jsonElement.getAsJsonObject().get( "date" ).getAsLong());

You could write an adapter for the whole MyType class so that you can keep standard date deserialization on other places (if any), and limit your specific deserialization only inside MyType.
To explain better what I mean, here's the code you can copy&paste&run:
package stackoverflow.questions.q14197557;
import java.lang.reflect.Type;
import java.util.Date;
import com.google.gson.*;
public class Q14197557 {
public static class MyType {
String user;
Date date;
#Override
public String toString() {
return "MySample [user=" + user + ", date=" + date + "]";
}
}
public static class MySampleDeserializer implements JsonDeserializer<MyType> {
public MyType deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
if (json == null)
return null;
else {
MyType s = new MyType();
s.user = json.getAsJsonObject().get("user").getAsString();
s.date = new Date(json.getAsJsonObject().get("date").getAsLong());
return s;
}
}
}
public static void main(String[] args) {
String json = "{ user: \"sample-user\", date : 1225864800 }";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyType.class, new MySampleDeserializer());
MyType s = gsonBuilder.create().fromJson(json, MyType.class);
System.out.println("My Type: " + s);
}
}

Related

Why is my DateTime deserializer is truncating DateTime's minute/second/millisecond?

I have a class that deserializes a JSON element.
public class DateTimeConverter implements JsonSerializer<DateTime>, JsonDeserializer<DateTime>
{
private static final DateTimeFormatter DATE_FORMAT = ISODateTimeFormat.dateHourMinuteSecondMillis();
#Override
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context)
{
final DateTimeFormatter fmt = ISODateTimeFormat.dateHourMinuteSecondMillis();
return new JsonPrimitive(fmt.print(src));
}
#Override
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
final String dateAsString = json.getAsString();
System.out.println(dateAsString);
if (json.isJsonNull() || dateAsString.length()==0)
{
return null;
}
else
{
return DATE_FORMAT.parseDateTime(json.getAsString());
}
}
}
However, my Deserialize method when I input:
2015-07-29T11:00:00.000Z
I receive:
2015-07-29T11
from the System.out.println(dateAsString); Why is it truncating my input?
I think my issue is within my test class:
I constructed a DateTime object to be used with Google's Gson. However, I think the default constructor for DateTimeType doesn't support minute/second/millisecond. Is there a way I can extend the DateTimeType to support it?
Here is my test class:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.joda.time.DateTime;
import org.junit.Test;
import java.lang.reflect.Type;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Tests {#link DateTimeConverter}.
*/
public class DateTimeConverterTest {
String testTime = "2015-07-29T11:00:00.001Z";
#Test
public void testDateTimeConverter() throws Exception {
final Gson gson = initCustomGSON();
Type DateTimeType = new TypeToken<DateTime>() {
}.getType();
System.out.println(testTime);
DateTimeConverter timeConverter = new DateTimeConverter();
DateTime m = (gson.fromJson(testTime, DateTimeType));
assertThat("11", is(m.hourOfDay().getAsText()));
}
public Gson initCustomGSON() {
final GsonBuilder builder = new GsonBuilder();
JodaTimeConverters converter = new JodaTimeConverters();
converter.registerAll(builder);
return builder.create();
}
}
You have a few issues with this code.
Your first problem is that : is an operator in Json. You are interpreting an unescaped String with a : in it, so Gson is interpreting it as key : value. Your test string needs to surround the entire text date with quotes to prevent this from happening, e.g.
String testTime = "\"2015-07-29T11:00:00.001Z\"";
You were using ISODateTimeFormat.dateHourMinuteSecondMillis() in your code. However, the format pattern for this is yyyy-MM-dd'T'HH:mm:ss.SSS, which as you can see does not include a time zone. You want to be using ISODateTimeFormat.dateTime(), whose pattern is yyyy-MM-dd'T'HH:mm:ss.SSSZZ, which does have a time zone.
private static final DateTimeFormatter DATE_FORMAT = ISODateTimeFormat.dateTime();
Once these two changes are made, the DateTime object is finally properly created... but it will be created in your local time zone, not in UTC (it will correctly adjust the time to your zone. You can easily switch this back to UTC by doing:
DateTime m = ((DateTime) (gson.fromJson(testTime, DateTimeType))).withZone(DateTimeZone.UTC);
Once you make these three changes, your tests will pass. However: I strongly advise against using JsonSerializer and JsonDeserializer, they have been deprecated in favor of TypeAdapter, whose streaming API is significantly more performant:
New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface's tree API.
I am aware the user guide provides code for how to do it with the JsonSerializer / JsonDeserializer API, but that's just because they haven't yet updated it.
It would simply be something like this:
public class DateTimeAdapter extends TypeAdapter<DateTime> {
private static final DateTimeFormatter FORMAT = ISODateTimeFormat.dateTime();
public DateTime read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
String dateString = reader.nextString();
if(dateString.length() == 0) return null;
return FORMAT.parseDateTime(dateString);
}
public void write(JsonWriter writer, DateTime value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.value(FORMAT.print(value));
}
}

JSON: use deserializing name different than serializing name json

I have one class User, I received JSON (for User class) from system1 and I should read info , validate then forward to system2, I can't touch these 2 systems, the problem is the names of keys are different, I want to differentiate between deserialized and serialized name
received JSON is :
{"userId":"user1","pwd":"123456","country":"US"}
"{"username":"user1","password":"123456","country":"US"}"
But the sent should be like this
I am using Gson lib, and this is my code:
User class:
class User implements Cloneable {
#SerializedName("username")
private String username ;
#SerializedName("password")
private String password ;
#SerializedName("country")
private String country ;
}
TestJson class
class TestJson {
private static GsonBuilder gsonBuilder;
private static Gson gson;
public static Object fromJson(String json, Class clz) {
gson = new Gson();
return gson.fromJson(json, clz);
}
public static String toJson(Object obj) {
gsonBuilder = new GsonBuilder();
gson = gsonBuilder.create();
String json = gson.toJson(obj);
return json;
}
public static void main(String[] args) {
String json2 = "{\"userId\":\"user1\",\"pwd\":\"123456\",\"country\":\"US\"}";
User user = (User) TestJson.fromJson(json2, User.class);
System.out.println(user.getPassword());
User u = new User("user1","123456","US");
String json1 = TestJson.toJson(u);
System.out.println(json1);
}
}
If there are alternative names of field just use alternate param of #SerializedName
public class User {
#SerializedName(value="username", alternate={"userId", "useriD"})
private String username ;
...
}
You can create custom serializer/deserializer for this purpose.
Serializer:
public class UserSerializer implements JsonSerializer<User> {
#Override public JsonElement serialize(User obj, Type type, JsonSerializationContext jsonSerializationContext) {
..........
}
}
Deserializer:
public class UserDeserializer implements JsonDeserializer<User> {
#Override public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
...........
}
}
and to create Gson instance:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(User.class, new UserSerializer());
gsonBuilder.registerTypeAdapter(User.class, new UserDeserializer());
Gson gson = gsonBuilder.create();
Example
Edit: this is an example of a custom deserializer which might fit into your need. We don't need a custom serializer in this case.
Add this UserDeserializer.java:
public class UserDeserializer implements JsonDeserializer<User> {
#Override
public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
User user = new User(obj.get("userId").getAsString(), obj.get("pwd").getAsString(), obj.get("country").getAsString());
return user;
}
}
Replace your fromJson implementation with this (I use generic to avoid the need for casting when calling fromJson):
public static <T> T fromJson(String json, Class<T> clz) {
gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(User.class, new UserDeserializer());
gson = gsonBuilder.create();
return gson.fromJson(json, clz);
}
The only way I can think of would be to have a custom Adapter or deser to a JsonObject and then map it to your User.
With Genson you can create two instances of Genson, one for deserialization and another one for serializaiton. The one used in deserialization could be configured with renamed properties like that.
// you can also precise that you want to rename only the properties from User class
Genson genson = new GensonBuilder()
.rename("username", "userId")
.rename("password", "pwd")
.create();
User user = genson.deserialize(json, User.class);

Gson: set date formatter for timestamp and timezone

What pattern should I use for date format 1418805300000-0100 ? (Timestamp and timezone)
GsonBuilder().setDateFormat("?????????????-Z")
Solution:
create new GSON with adapters
private static Gson createGson(){
return new GsonBuilder().disableHtmlEscaping()
.registerTypeHierarchyAdapter(Date.class, new DateTimeSerializer())
.registerTypeHierarchyAdapter(Date.class, new DateTimeDeserializer())
.create();
}
public static MyClass fromJson(String json) {
return createGson().fromJson(json, MyClass.class);
}
public String toJson() {
return createGson().toJson(this);
}
JSON Serializer
private static class DateTimeSerializer implements JsonSerializer<Date> {
#Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
// hodgie code
return new JsonPrimitive(src.getTime() + new SimpleDateFormat("Z").format(src));
}
}
Deserializer
private static class DateTimeDeserializer implements JsonDeserializer<Date> {
#Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// hodgie code
return new Date(Long.valueOf((json).getAsString().substring(0, 13)));
}
}
GsonBuilder#setDateFormat(String) uses the String provided as an argument for creating SimpleDateFormat instances. SimpleDateFormat does not provide any patterns for generating a timestamp. You won't be able to achieve what you want with setDateFormat. A custom TypeAdapter seems appropriate.

Gson Polymorphic Serialization

using Gson 2.2.2 I'm trying to serialize an array list of POJOs (Behaviors).
i have an adapter that's pretty much a copy of what i've seen online:
public class BehaviorAdapter implements JsonSerializer<Behavior> {
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
#Override
public JsonElement serialize(Behavior src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject retValue = new JsonObject();
String className = src.getClass().getCanonicalName();
retValue.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(src);
retValue.add(INSTANCE, elem);
return retValue;
}
}
The i register it like this:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(Behavior.class, new BehaviorAdapter());
gson = builder.create();
Then when i try to serialize my ArrayList:
String json2 = gson.toJson(behaviors);
I get a stack overflow.
It appears that on line:
JsonElement elem = context.serialize(src);
It starts a recursive loop, going again and again through my serializer. So How do i register it so that this won't happen? I need to serialize the list and maintain polymorphism.
Looks like you found the infinite loop the JsonSerializer docs warn about:
However, you should never invoke it on the src object itself since that will cause an infinite loop (Gson will call your call-back method again).
The easiest way I can think of is to create a new Gson instance that does not have the handler installed, and run your instances through that.
As an end run, you could just serialize the List<Behavior> instead:
public class BehaviorListAdapter implements JsonSerializer<List<Behavior>> {
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
#Override
public JsonElement serialize(List<Behavior> src, Type typeOfSrc,
JsonSerializationContext context) {
JsonArray array = new JsonArray();
for (Behavior behavior : src) {
JsonObject behaviorJson = new JsonObject();
String className = behavior.getClass().getCanonicalName();
behaviorJson.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(behavior);
behaviorJson.add(INSTANCE, elem);
array.add(behaviorJson);
}
return array;
}
}
GsonBuilder builder = new GsonBuilder();
// use a TypeToken to make a Type instance for a parameterized type
builder.registerTypeAdapter(
(new TypeToken<List<Behavior>>() {}).getType(),
new BehaviorListAdapter());
gson = builder.create();
Take a look at RuntimeTypeAdapterFactory. The test for that class has an example:
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class)
.registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(rta)
.create();
CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson(
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard);
This class isn't in core Gson; you'll need to copy it into your project to use it.
I get what you are trying to do here, and i had the same issue.
I ended writing a simple abstract class
public abstract class TypedJsonizable extends Jsonizable {}
and registering a TypeHierarchyAdapter to my Gson instance
protected static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter
(TypedJsonizable.class,new TypedJsonizableSerializer());
The key for this TypeAdapter is not to invoke context.serialize and context.deserialize cause this would cause an infinite loop as stated from Jeff Bowman in his answer, this TypeAdapter use reflection to avoid that.
import com.google.gson.*;
import org.apache.log4j.Logger;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
public class TypedJsonizableSerializer implements JsonSerializer<TypedJsonizable>, JsonDeserializer<TypedJsonizable> {
static final String CLASSNAME_FIELD = "_className";
Logger logger = Logger.getLogger(TypedJsonizable.class);
#Override
public JsonElement serialize(TypedJsonizable src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject contentObj = new JsonObject();
contentObj.addProperty(CLASSNAME_FIELD,src.getClass().getCanonicalName());
for (Field field : src.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
if (field.get(src)!=null)
contentObj.add(field.getName(),context.serialize(field.get(src)));
} catch (IllegalAccessException e) {
logger.error(e.getMessage(),e);
}
}
return contentObj;
}
#Override
public TypedJsonizable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String className = jsonObject.get(CLASSNAME_FIELD).getAsString();
if (className == null || className.isEmpty())
throw new JsonParseException("Cannot find _className field. Probably this instance has not been serialized using Jsonizable jsonizer");
try {
Class<?> clazz = Class.forName(className);
Class<?> realClazz = (Class<?>) typeOfT;
if (!realClazz.equals(clazz))
throw new JsonParseException(String.format("Cannot serialize object of class %s to %s", clazz.getCanonicalName(),realClazz.getCanonicalName()));
Object o = clazz.getConstructor().newInstance();
for (Field field : o.getClass().getDeclaredFields()) {
field.setAccessible(true);
if (jsonObject.has(field.getName())) {
field.set(o,context.deserialize(jsonObject.get(field.getName()) , field.getGenericType()));
}
}
return (TypedJsonizable) o;
} catch (ClassNotFoundException e) {
throw new JsonParseException(String.format("Cannot find class with name %s . Maybe the class has been refactored or sender and receiver are not using the same jars",className));
} catch (IllegalAccessException e){
throw new JsonParseException(String.format("Cannot deserialize, got illegalAccessException %s ",e.getMessage()));
} catch (NoSuchMethodException | InstantiationException | InvocationTargetException e) {
throw new JsonParseException(String.format("Cannot deserialize object of class %s, unable to create a new instance invoking empty constructor",className));
}
}
}
I've found another solution (workaround) for this issue: don't serialize base class in your class hierarchy but use descendants. For example:
...
protected static Gson gson;
...
GsonBuilder gsb = new GsonBuilder();
gsb.registerTypeAdapter(SomeBase.class, new MQPolymorphicSerializer<SomeBase>());
gson = gsb.create();
SomeBase:
public class SomeBase{
...
}
SomeDescendant:
public class SomeDescendant extends SomeBase {
...
}
Stack overflow exception case:
gson.toJson(new SomeBase());
Workaround case:
gson.toJson(new SomeDescendant());
...and finally - serializer example:
public class MQPolymorphicSerializer<T> implements JsonSerializer<T>, JsonDeserializer<T> {
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
#Override
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject retValue = new JsonObject();
String className = src.getClass().getName();
retValue.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(src);
retValue.add(INSTANCE, elem);
return retValue;
}
#Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
String className = prim.getAsString();
Class<?> klass = null;
try {
klass = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e.getMessage());
}
return context.deserialize(jsonObject.get(INSTANCE), klass);
}
}

How to handle Java class which has Class<?> to convert to JSON using Gson

For this class
public class Data {
Map<String, Class<?>> dataTypes = new HashMap<String, Class<?>>();
}
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.serializeNulls()
.create();
Data d = new Data();
d.dataTypes.put("DateParameter", java.util.Date.class);
System.out.println("Data is " + gson.toJson(d));
Prints the following:
Data is {"dataTypes":{"DateParameter":{"annotationType":null}}}
How do get the correct class type printed?
I think you have to write your own custom json de/serializer for the Class class. Try something like this:
public static class ClassSerializer implements
JsonSerializer<Class>, JsonDeserializer<Class> {
public JsonElement serialize(Class klass, Type type, JsonSerializationContext ctx) {
return new JsonPrimitive(klass.getCanonicalName());
}
public Class deserialize(JsonElement el, Type type, JsonDeserializationContext ctx) throws JsonParseException {
try {
return Class.forName(el.getAsJsonPrimitive().getAsString());
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException(cnfe);
}
}
}
// ...
public static void main(String args[]) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Class.class, new ClassSerializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.serializeNulls()
.create();
Data d = new Data();
d.dataTypes.put("DateParameter", java.util.Date.class);
gson.toJson(d); // => {"dataTypes":{"DateParameter":"java.util.Date"}}

Categories