Jackson入门及java2json转换保留TimeZone问题

  注意,本文用到的Jackson版本为2.2.3。 Java Object和json之间的转换(Object -> json为serialize,反之为deserialize)是通过ObjectMapper类来实现,最简单是实现如下:

//被转换的对象
public class Person {

    String firstname;

    String lastname;

    String phone;

    List<Address> addresses = new ArrayList<Address>();

    //这里省略setter、getter

    public Address addNewAddress() {
        Address address = new Address();
        addresses.add(address);
        return address;
    }

    public String getFullName() {
        return firstname+"/"+lastname;
    }

    static class Address{

        String street;

        String city;

        Integer number;

     //这里省略setter、getter
    }
}

public class JacksonTest {

    public static void main(String[] args) {
        try {
            Person person = new Person();
            person.setFirstname("dennis");
            person.setLastname("lau");
            person.setPhone("123456789");
            Person.Address address = person.addNewAddress();
            address.setCity("shenzhen");
            address.setStreet("renming road");
            address.setNumber(1234);

            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(person);
            System.out.println(json);
        }catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }
}
输出:{"firstname":"dennis","lastname":"lau","phone":"123456789","addresses":[{"street":"renming road","city":"shenzhen","number":1234}],"fullName":"dennis/lau"}

  使用默认构站器的ObjectMapper在默认配置下将会序列化含有getter的值(不一定有真实相对应的变量成员,如Person的getFullName),如果我们只想序列化类对应的field,有两个解决办法:

1.使用Jackson Annotations

  使用Jackson Annotation的@JsonIgnore,将该注释加在不想序列化的getter方法,就能达到在序列化时忽略该field的效果:

//其余配置 保持不变
@JsonIgnore public String getFullName() { return firstname+"/"+lastname; }
输出:{"firstname":"dennis","lastname":"lau","phone":"123456789","addresses":[{"street":"renming road","city":"shenzhen","number":1234}]} //对比未加注释,这里的fullName不存在了。

2.更改MapperFeature的配置

  更改ObjectMapper实例的MapperFeature的AUTO_DETECT_GETTERS为disbale,这样序列化时所有的getter都不会被检测(如果filed的访问范围为public,还是可见的,能够被序列化为json的属性,否则都不会被序列化),再在需要序列化的field对应的getter方法加上Annotation @JsonProperty(因为一般情况下field都是private或者package可见)。

public class Person {

    String firstname;

    String lastname;

    String phone;

    List<Address> addresses = new ArrayList<Address>();

    @JsonProperty
    public String getFirstname() {
        return firstname;
    }


    @JsonProperty
    public String getLastname() {
        return lastname;
    }


    @JsonProperty
    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @JsonProperty
    public List<Address> getAddresses() {
        return addresses;
    }

    //省略了setter

    public String getFullName() {
        return firstname+"/"+lastname;
    }

public class JacksonTest {

    public static void main(String[] args) {
        try {
            Person person = new Person();
            person.setFirstname("dennis");
            person.setLastname("lau");
            person.setPhone("123456789");
            Person.Address address = person.addNewAddress();
            address.setCity("shenzhen");
            address.setStreet("renming road");
            address.setNumber(1234);

            ObjectMapper objectMapper = new ObjectMapper();
//序列化之前,disable getter的自动检测,默认情况为使能的。 objectMapper.disable(MapperFeature.AUTO_DETECT_GETTERS); String json = objectMapper.writeValueAsString(person); System.out.println(json); }catch (Exception e) { e.printStackTrace(System.out); } } }
输出:{"firstname":"dennis","lastname":"lau","phone":"123456789"}

Java Object和json相互转换过程中,保留时区信息

  默认情况下,Jackson对Date类型序列化为TimeStamp形式(为long类型的数字)

        long now = System.currentTimeMillis();
        Date date = new Date(now);
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(date);
        System.out.println(now);
        System.out.println(s);
输出:1449391557113, 1449391557113

  TimeStamps形式可读性很差,并且对于带有时区的时间,并不能保留时区信息,造成同样的时间,经过序列化,再反序列化后时间改变了。为了实现转换保留时区信息,需要自定义

JsonSerializer和JsonDeserializer:
public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

//序列化时,使用自定义的SimpleDateFormat来格式化时间 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSSZ"); @Override public void serialize(Calendar calendar, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { sdf.setCalendar(calendar);//时区信息保留在Calendar里面,Date类型是没有时区信息的哦 jsonGenerator.writeString(sdf.format(calendar.getTime())); } } public class CustomCalenderDeserializer extends JsonDeserializer<Calendar> {
//反序列化时,为了方便,使用org.joda.time.format.DateTimeFormatter,它能够根据预设的时间格式(注意这里的格式要和序列化时的一致),还原出时间,并可以得到GregorianCalendar
private static DateTimeFormatter stf = DateTimeFormat.forPattern("yyyy-MM-dd\'T\'HH:mm:ss.SSSZ");
@Override
public Calendar deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String date = jsonParser.getText();
return stf.withOffsetParsed().parseDateTime(date).toGregorianCalendar();
}
}

  现在看看如何使用自定义的序列化及反序列化对象:

public class Test {

    Calendar calendar;

    String desc;

//使用Annotation来使用自定义serializer, @JsonSerialize(using = CustomCalendarSerializer.class) public Calendar getCalendar() { return calendar; }
//使用Annotation来使用自定义Deserializer @JsonDeserialize(using = CustomCalenderDeserializer.class) public void setCalendar(Calendar calendar) { this.calendar = calendar; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } } public class JacksonTest { public static void main(String[] args) { try { String date = "2000-10-01T10:10:10.123"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSS");        //创建被转换的对象calendar,有时区信息 Calendar calendar = new GregorianCalendar(); calendar.setTime(sdf.parse(date)); TimeZone timeZone = TimeZone.getTimeZone("GMT-8:00"); calendar.setTimeZone(timeZone); Test test = new Test(); test.setCalendar(calendar); test.setDesc("test date with timezone to json"); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(test); System.out.println(json); Test restore = objectMapper.readValue(json, Test.class); System.out.print(restore.getCalendar().getTimeZone().getDisplayName()); }catch (Exception e) { e.printStackTrace(System.out); } } }
输出:

before: 970366210123

before: GMT-08:00

{"calendar":"2000-09-30T18:10:10.123-0800","desc":"test date with timezone to json"}

after: 970366210123

after: GMT-08:00

  使用了自定义格式的serializer,json的时间格式可读性就很好了,并且转换前后时间及时区都没有改变