Search code examples
javaserializationjson-deserializationjsonserializergenson

Genson serialization Issue with byte and Date fields


Hi I am using Genson for ser/de for my POJO classes. But there is a problem while serializing a POJO which has a byte field. I haven't tried deserialization of same object. So I don't know whether that's also working. Also there is problem in date field.

Here is my POJO

public class User implements Serializable{

    private String userId;
    private String emailId;
    private Date lastLogin;
    private Byte attempts;

    //geters and setters, toString methods.
}

I got object with values and when I print I got like following.

User [userId=sss, [email protected], lastLogin=2014-12-21 23:24:46.0, attempts=9]

I tried the following code for serialization with Genson (Sometimes I need to serialize by removing some fields, so I'm using the following code).

public String serialize(Object object, Class cls,
            String[] ignoreList) {
        GensonBuilder builder = new Genson.Builder();
        if (null != ignoreList) {
            for (String field : ignoreList) {
                builder.exclude(field, cls);
            }
        }
        Genson genson = builder.create();
        return genson.serialize(object);
    }

But I get the following JSON string, byte field value is different. Date field value is without time stamp.

{"emailId":"[email protected]","lastLogin":"21 Dec, 2014","attempts":"CQ==","userId":"sss"}

I see the byte problem is fixed already with this Issue in Github. But How do I get the updated code in my project? I'm using Maven and I'm using latest version of Genson-1.2.

Should I try builder.useByteAsInt(true); in my code?

Also for Date field. I see two options

builder.useDateAsTimestamp(true);
builder.useDateFormat(dateFormat);

I don't want to Set these things. Because sometimes the field may be coming with timestamp and sometimes it will be without timestamp and date formates can be different for different objects. I'm lookiung for a solution which will serialize date field and convert to string as it is.

What is the best way to get the proper JSON string? I'm looking for a solution which can serialize deserialize any POJO class.

UPDATE 1

I have created ByteConverter and Date Converter.

These are my converters ByteConverter.java

@HandleClassMetadata
@HandleBeanView
public final class ByteConverter implements Converter<Byte> {
    public final static ByteConverter instance = new ByteConverter();

    private ByteConverter() {
    }

    public void serialize(Byte obj, ObjectWriter writer, Context ctx) {
        writer.writeValue(obj.byteValue());
    }

    public Byte deserialize(ObjectReader reader, Context ctx) {
        return (byte) reader.valueAsInt();
    }
}

DateConverter.java

@HandleClassMetadata
@HandleBeanView
public class DateConverter implements Converter<Date> {
    private DateFormat dateFormat;
    private final boolean asTimeInMillis;

    public DateConverter() {
        this(SimpleDateFormat.getDateInstance(), false);
    }

    public DateConverter(DateFormat dateFormat, boolean asTimeInMillis) {
        if (dateFormat == null)
            dateFormat = SimpleDateFormat.getDateInstance();
        this.dateFormat = dateFormat;
        this.asTimeInMillis = asTimeInMillis;
    }

    public void serialize(Date obj, ObjectWriter writer, Context ctx) {
        if (asTimeInMillis)
            writer.writeValue(obj.getTime());
        else
            writer.writeUnsafeValue(format(obj));
    }

    protected synchronized String format(Date date) {
        return dateFormat.format(date);
    }

    public Date deserialize(ObjectReader reader, Context ctx) {
        try {
            if (asTimeInMillis)
                return new Date(reader.valueAsLong());
            else
                return read(reader.valueAsString());
        } catch (ParseException e) {
            throw new JsonBindingException("Could not parse date "
                    + reader.valueAsString(), e);
        }
    }

    protected synchronized Date read(String dateString) throws ParseException {
        return dateFormat.parse(dateString);
    }
}

In my method I tried following code

public void serialize(User user) {
    Converter<Byte> byteConverter = ByteConverter.instance;
                Converter<Date> dateConverter = new DateConverter();
                Converter[] converters = { byteConverter, dateConverter };
                GensonBuilder builder = new GensonBuilder();
                builder.withConverters(converters);
                Genson genson = builder.create();
                String str = genson.serialize(user);
                System.out.println(str);
}

I get the following JSON string

{"emailId":"[email protected]","lastLogin":"21 Dec, 2014","attempts":"9","userId":"sss"}

Byte problem is solved. But Date problem is still there. I tried following derialize method of DateConverter class. But it also didn't work.

public Date deserialize(ObjectReader reader, Context ctx) {
        try {
            if (reader.getValueType() == ValueType.INTEGER) 
                 return new Date(reader.valueAsLong());
            else 
                 return dateFormat.parse(reader.valueAsString());
        } catch (ParseException e) {
            throw new JsonBindingException("Could not parse date "
                    + reader.valueAsString(), e);
        }
    }

UPDATE 2

My Updated DateConverter.java. DateUtils is from Apache commons. I haven't tested deserialize. But Serialize is working. I wanted the format with Timezone also. So I did in my customized way.

@HandleClassMetadata
@HandleBeanView
public class DateConverter implements Converter<Date> {

    private static final String YYYY_MM_DD_HH_MM_SS_Z = "yyyy-MM-dd HH:mm:ss z";
    private static final String YYYY_MM_DD_SLASH = "yyyy/MM/dd";
    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    private static final String YYYY_MM_DD_HH_MM_SS_ZZZ = "yyyy-MM-dd HH:mm:ss zzz";
    private static final String DD_MMM_YY_HH_MM_SS_SSSSSS_AAA = "dd-MMM-yy hh.mm.ss.SSSSSS aaa";
    private static final String YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
    private static final String YYYY_MM_DD = "yyyy-MM-dd";
    private static final String YYYY_MM_DD_HH_MM_SS_0 = "yyyy-MM-dd HH:mm:ss.'0'";
    private static final String EEE_MMM_DD_HH_MM_SS_ZZZ_YYYY = "EEE MMM dd HH:mm:ss zzz yyyy";

    String[] dateFormats = { EEE_MMM_DD_HH_MM_SS_ZZZ_YYYY,
            YYYY_MM_DD_HH_MM_SS_0, YYYY_MM_DD, YYYY_MM_DD_HH_MM_SS_SSS,
            DD_MMM_YY_HH_MM_SS_SSSSSS_AAA, YYYY_MM_DD_HH_MM_SS_ZZZ,
            YYYY_MM_DD_HH_MM_SS, YYYY_MM_DD_SLASH };

    private DateFormat dateFormat;
    private final boolean asTimeInMillis;

    public DateConverter() {
        this(SimpleDateFormat.getDateInstance(), false);
    }

    public DateConverter(DateFormat dateFormat, boolean asTimeInMillis) {
        if (dateFormat == null)
            dateFormat = SimpleDateFormat.getDateInstance();
        this.dateFormat = dateFormat;
        this.asTimeInMillis = asTimeInMillis;
    }

    public void serialize(Date obj, ObjectWriter writer, Context ctx) {
        if (asTimeInMillis)
            writer.writeValue(obj.getTime());
        else
            writer.writeUnsafeValue(format(obj));
    }

    protected synchronized String format(Date date) {
        return getDateTime(YYYY_MM_DD_HH_MM_SS_Z, date);
        // return dateFormat.format(date);
    }

    public Date deserialize(ObjectReader reader, Context ctx) {
        try {
            if (asTimeInMillis)
                return new Date(reader.valueAsLong());
            else
                return read(reader.valueAsString());
        } catch (ParseException e) {
            throw new JsonBindingException("Could not parse date "
                    + reader.valueAsString(), e);
        }
    }

    protected synchronized Date read(String dateString) throws ParseException {
        return DateUtils.parseDate(dateString, dateFormats);
//      return dateFormat.parse(dateString);
    }

    public String getDateTime(String aMask, Date aDate) {
        SimpleDateFormat df = null;
        String returnValue = "";

        if (aDate != null) {
            df = new SimpleDateFormat(aMask);
            returnValue = df.format(aDate);
        }
        return returnValue;
    }
}

Solution

  • Update This has been released in Genson 1.3.

    The single byte issue

    The fix for the issue you mention has been pushed to the devlopment branch but not yet released. It should come soon with other enhancements and bug fixes. If you need it now, then you can define a custom Converter and register it with GensonBuilder.

    Note that you want to add @HandleClassMetadata annotation only if you have enabled class metadata feature (this annotation ensures that Genson will not write class info for the output of this Converter).

    The date format

    If the incoming date formats (string or timestamp) dont change per field then you can define the global policy and then override with @JsonDateFormat annotation on your getter and setter (if you use the default config) or fields (if you don't use getter/setter and configured visibility of private fields).

    If the incoming data changes even for the same field (or you don't want to use an annotation) you will probably have to provide a custom Date Converter similar to this one. The main difference would be that in the deserialize method you will have something like this:

    if (reader.getValueType() == ValueType.INTEGER) 
      return new Date(reader.valueAsLong());
    else if (reader.getValueType() == ValueType.STRING) 
      return dateFormat.parse(reader.valueAsString());
    

    I have opened an issue to provide this out of the box in the next release.