Gson:无法在 Android 中正确反序列化 Joda Time LocalDateTime 值

Gson: Cannot deserialization Joda Time LocalDateTime value properly in Android

我在 Java 网络服务器(Jersey Jax RS RI 2.13)上有一个 HTTP REST API 运行,它为我提供了 ArrayList AssetBooking 用 Ja​​ckson 序列化的对象。在 Android 方面,我有相同的对象,我用 Gson 对其进行了反序列化。

所有其他对象都反序列化得很好,AssetBooking 对象的其他字段也反序列化得很好...

这是我的反序列化方法:

public ArrayList<AssetBooking> getAssetBookings (String json) {

        Gson gson = new Gson();

        ArrayList<AssetBooking> assetBookings = gson.fromJson(json, new TypeToken<ArrayList<AssetBooking>>(){}.getType());

        return assetBookings;

    }

问题是我的 Joda Time LocalDateTime 字段被反序列化为当前时间戳,而不是我从服务器传递的日期(在 JSON 字符串中是正确的) .

您知道问题的可能原因吗?

AssetBooking.java

import org.joda.time.*;

public class AssetBooking {

    protected int id;
    protected int assetId;
    protected int userId;
    protected LocalDateTime fromDatetime;
    protected LocalDateTime toDatetime;
    protected boolean status;
    protected LocalDateTime createdOn;
    protected LocalDateTime updatedOn;
    protected String userName;
    protected String userLastName;
    protected String userEmail;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAssetId() {
        return assetId;
    }

    public void setAssetId(int assetId) {
        this.assetId = assetId;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public LocalDateTime getFromDatetime() {
        return fromDatetime;
    }

    public void setFromDatetime(LocalDateTime fromDatetime) {
        this.fromDatetime = fromDatetime;
    }

    public LocalDateTime getToDatetime() {
        return toDatetime;
    }

    public void setToDatetime(LocalDateTime toDatetime) {
        this.toDatetime = toDatetime;
    }

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public LocalDateTime getCreatedOn() {
        return createdOn;
    }

    public void setCreatedOn(LocalDateTime createdOn) {
        this.createdOn = createdOn;
    }

    public LocalDateTime getUpdatedOn() {
        return updatedOn;
    }

    public void setUpdatedOn(LocalDateTime updatedOn) {
        this.updatedOn = updatedOn;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserLastName() {
        return userLastName;
    }

    public void setUserLastName(String userLastName) {
        this.userLastName = userLastName;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }
}

JSON 在 Android 端收到的字符串(它只有一个对象,但我不得不削减日期变量 createdOnupdatedOn 所以它不会爆炸Whosebug 最大 post 大小):

[
    {
        "id": 0,
        "assetId": 1,
        "userId": 1,
        "fromDatetime": {
            "year": 2017,
            "dayOfMonth": 12,
            "dayOfWeek": 1,
            "era": 1,
            "dayOfYear": 163,
            "chronology": {
                "zone": {
                    "fixed": true,
                    "id": "UTC"
                }
            },
            "centuryOfEra": 20,
            "yearOfEra": 2017,
            "yearOfCentury": 17,
            "weekyear": 2017,
            "monthOfYear": 6,
            "weekOfWeekyear": 24,
            "hourOfDay": 13,
            "minuteOfHour": 14,
            "secondOfMinute": 15,
            "millisOfSecond": 0,
            "millisOfDay": 47655000,
            "fields": [
                {
                    "lenient": false,
                    "minimumValue": -292275054,
                    "maximumValue": 292278993,
                    "leapDurationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "rangeDurationField": null,
                    "durationField": {
                        "precise": false,
                        "unitMillis": 31556952000,
                        "name": "years",
                        "type": {
                            "name": "years"
                        },
                        "supported": true
                    },
                    "name": "year",
                    "type": {
                        "durationType": {
                            "name": "years"
                        },
                        "rangeDurationType": null,
                        "name": "year"
                    },
                    "supported": true
                },
                {
                    "lenient": false,
                    "minimumValue": 1,
                    "maximumValue": 12,
                    "leapDurationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "rangeDurationField": {
                        "precise": false,
                        "unitMillis": 31556952000,
                        "name": "years",
                        "type": {
                            "name": "years"
                        },
                        "supported": true
                    },
                    "durationField": {
                        "precise": false,
                        "unitMillis": 2629746000,
                        "name": "months",
                        "type": {
                            "name": "months"
                        },
                        "supported": true
                    },
                    "name": "monthOfYear",
                    "type": {
                        "durationType": {
                            "name": "months"
                        },
                        "rangeDurationType": {
                            "name": "years"
                        },
                        "name": "monthOfYear"
                    },
                    "supported": true
                },
                {
                    "minimumValue": 1,
                    "maximumValue": 31,
                    "rangeDurationField": {
                        "precise": false,
                        "unitMillis": 2629746000,
                        "name": "months",
                        "type": {
                            "name": "months"
                        },
                        "supported": true
                    },
                    "lenient": false,
                    "durationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "unitMillis": 86400000,
                    "name": "dayOfMonth",
                    "type": {
                        "durationType": {
                            "name": "days"
                        },
                        "rangeDurationType": {
                            "name": "months"
                        },
                        "name": "dayOfMonth"
                    },
                    "supported": true,
                    "leapDurationField": null
                },
                {
                    "maximumValue": 86399999,
                    "range": 86400000,
                    "rangeDurationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "lenient": false,
                    "durationField": {
                        "name": "millis",
                        "type": {
                            "name": "millis"
                        },
                        "supported": true,
                        "precise": true,
                        "unitMillis": 1
                    },
                    "minimumValue": 0,
                    "unitMillis": 1,
                    "name": "millisOfDay",
                    "type": {
                        "durationType": {
                            "name": "millis"
                        },
                        "rangeDurationType": {
                            "name": "days"
                        },
                        "name": "millisOfDay"
                    },
                    "supported": true,
                    "leapDurationField": null
                }
            ],
            "values": [
                2017,
                6,
                12,
                47655000
            ],
            "fieldTypes": [
                {
                    "durationType": {
                        "name": "years"
                    },
                    "rangeDurationType": null,
                    "name": "year"
                },
                {
                    "durationType": {
                        "name": "months"
                    },
                    "rangeDurationType": {
                        "name": "years"
                    },
                    "name": "monthOfYear"
                },
                {
                    "durationType": {
                        "name": "days"
                    },
                    "rangeDurationType": {
                        "name": "months"
                    },
                    "name": "dayOfMonth"
                },
                {
                    "durationType": {
                        "name": "millis"
                    },
                    "rangeDurationType": {
                        "name": "days"
                    },
                    "name": "millisOfDay"
                }
            ]
        },
        "toDatetime": {
            "year": 2017,
            "dayOfMonth": 13,
            "dayOfWeek": 4,
            "era": 1,
            "dayOfYear": 194,
            "chronology": {
                "zone": {
                    "fixed": true,
                    "id": "UTC"
                }
            },
            "centuryOfEra": 20,
            "yearOfEra": 2017,
            "yearOfCentury": 17,
            "weekyear": 2017,
            "monthOfYear": 7,
            "weekOfWeekyear": 28,
            "hourOfDay": 14,
            "minuteOfHour": 15,
            "secondOfMinute": 16,
            "millisOfSecond": 0,
            "millisOfDay": 51316000,
            "fields": [
                {
                    "lenient": false,
                    "minimumValue": -292275054,
                    "maximumValue": 292278993,
                    "leapDurationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "rangeDurationField": null,
                    "durationField": {
                        "precise": false,
                        "unitMillis": 31556952000,
                        "name": "years",
                        "type": {
                            "name": "years"
                        },
                        "supported": true
                    },
                    "name": "year",
                    "type": {
                        "durationType": {
                            "name": "years"
                        },
                        "rangeDurationType": null,
                        "name": "year"
                    },
                    "supported": true
                },
                {
                    "lenient": false,
                    "minimumValue": 1,
                    "maximumValue": 12,
                    "leapDurationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "rangeDurationField": {
                        "precise": false,
                        "unitMillis": 31556952000,
                        "name": "years",
                        "type": {
                            "name": "years"
                        },
                        "supported": true
                    },
                    "durationField": {
                        "precise": false,
                        "unitMillis": 2629746000,
                        "name": "months",
                        "type": {
                            "name": "months"
                        },
                        "supported": true
                    },
                    "name": "monthOfYear",
                    "type": {
                        "durationType": {
                            "name": "months"
                        },
                        "rangeDurationType": {
                            "name": "years"
                        },
                        "name": "monthOfYear"
                    },
                    "supported": true
                },
                {
                    "minimumValue": 1,
                    "maximumValue": 31,
                    "rangeDurationField": {
                        "precise": false,
                        "unitMillis": 2629746000,
                        "name": "months",
                        "type": {
                            "name": "months"
                        },
                        "supported": true
                    },
                    "lenient": false,
                    "durationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "unitMillis": 86400000,
                    "name": "dayOfMonth",
                    "type": {
                        "durationType": {
                            "name": "days"
                        },
                        "rangeDurationType": {
                            "name": "months"
                        },
                        "name": "dayOfMonth"
                    },
                    "supported": true,
                    "leapDurationField": null
                },
                {
                    "maximumValue": 86399999,
                    "range": 86400000,
                    "rangeDurationField": {
                        "precise": true,
                        "unitMillis": 86400000,
                        "name": "days",
                        "type": {
                            "name": "days"
                        },
                        "supported": true
                    },
                    "lenient": false,
                    "durationField": {
                        "name": "millis",
                        "type": {
                            "name": "millis"
                        },
                        "supported": true,
                        "precise": true,
                        "unitMillis": 1
                    },
                    "minimumValue": 0,
                    "unitMillis": 1,
                    "name": "millisOfDay",
                    "type": {
                        "durationType": {
                            "name": "millis"
                        },
                        "rangeDurationType": {
                            "name": "days"
                        },
                        "name": "millisOfDay"
                    },
                    "supported": true,
                    "leapDurationField": null
                }
            ],
            "values": [
                2017,
                7,
                13,
                51316000
            ],
            "fieldTypes": [
                {
                    "durationType": {
                        "name": "years"
                    },
                    "rangeDurationType": null,
                    "name": "year"
                },
                {
                    "durationType": {
                        "name": "months"
                    },
                    "rangeDurationType": {
                        "name": "years"
                    },
                    "name": "monthOfYear"
                },
                {
                    "durationType": {
                        "name": "days"
                    },
                    "rangeDurationType": {
                        "name": "months"
                    },
                    "name": "dayOfMonth"
                },
                {
                    "durationType": {
                        "name": "millis"
                    },
                    "rangeDurationType": {
                        "name": "days"
                    },
                    "name": "millisOfDay"
                }
            ]
        },
        "status": true,
        "userName": "Fabio",
        "userLastName": "Lanza",
        "userEmail": "fabio@blabla.bla"
    }
]

作为实验,我在反序列化后序列化了对象(不是数组)并得到了这个结果:

{
  "assetId": 1,
  "createdOn": {
    "iChronology": {
      "iBase": {
        "iMinDaysInFirstWeek": 4
      }
    },
    "iLocalMillis": 1492419018809
  },
  "fromDatetime": {
    "iChronology": {
      "iBase": {
        "iMinDaysInFirstWeek": 4
      }
    },
    "iLocalMillis": 1492419014536
  },
  "id": 0,
  "status": true,
  "toDatetime": {
    "iChronology": {
      "iBase": {
        "iMinDaysInFirstWeek": 4
      }
    },
    "iLocalMillis": 1492419018793
  },
  "updatedOn": {
    "iChronology": {
      "iBase": {
        "iMinDaysInFirstWeek": 4
      }
    },
    "iLocalMillis": 1492419018831
  },
  "userEmail": "fabio@blabla.bla",
  "userId": 1,
  "userLastName": "Lanza",
  "userName": "Fabio"
}

您的 JSON 无缘无故地非常臃肿。请注意,并非每个 class 都设计为(反)序列化,尤其是对于像 Jackson 或 Gson 这样的非标准库(为什么 Joda Time 应该关心 Gson 和 Jackson 本身的任何原因?)。这两个库足够聪明,可以使用 Java 反射进行(反)序列化,但是他们不知道给定的 class 是否有理由被(反)序列化。如果您在两边使用相同库的不同版本,事情可能会变得更糟,因为您无法确定这些对象是否二进制兼容。更重要的是:你永远不应该对特定的对象二进制结构做出任何假设并只使用它的 public API 为了你的利益 。您所要做的就是让这些库知道这些 classes 并定义它们的实例被(反)序列化的方式。

为简单起见,您可以 encode/decode LocalDateTime 使用字符串的实例:这是最简单的方法,并且与 Joda Time 完美搭配:

  • LocalDateTime.toString()进行编码;
  • LocalDateTime.parse()解码。

例如,一个简单的值 new LocalDateTime(2017, 4, 16, 17, 15) 可以 "toStringed" 为 2017-04-16T17:15:00.000。这足以从中恢复原始日期。当然,如果需要,您可以使用自定义格式化程序。

"Server"

final class Server {

    private Server() {
    }

    static InputStream produceResponse()
            throws IOException {
        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        objectMapper.writeValue(byteArrayOutputStream, payload);
        return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    }

    private static final LocalDateTime date = new LocalDateTime(2017, 4, 16, 17, 15);

    private static final List<AssetBookingJacksonDto> payload = ImmutableList.of(
            new AssetBookingJacksonDto(1, 10, 100, date, date, true, date, date, "foo", "bar", "foo.bar@email")
    );

    private static final ObjectMapper objectMapper = createObjectMapper();

    private static ObjectMapper createObjectMapper() {
        final ObjectMapper objectMapper = new ObjectMapper();
        // Here we just define that we don't need getters and will use fields for brevity   
        return objectMapper
                .setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
                        .withFieldVisibility(ANY)
                        .withGetterVisibility(NONE)
                        .withSetterVisibility(NONE)
                        .withCreatorVisibility(NONE)
                )
                // Here is where LocalDateTime serialization strategy is registered
                .registerModule(new SimpleModule()
                        .addSerializer(LocalDateTime.class, new LocalDateTimeJsonSerializer())
                );
    }

    @SuppressWarnings("unused")
    private static final class AssetBookingJacksonDto {

        private final int id;
        private final int assetId;
        private final int userId;
        private final LocalDateTime fromDatetime;
        private final LocalDateTime toDatetime;
        private final boolean status;
        private final LocalDateTime createdOn;
        private final LocalDateTime updatedOn;
        private final String userName;
        private final String userLastName;
        private final String userEmail;

        private AssetBookingJacksonDto(final int id, final int assetId, final int userId, final LocalDateTime fromDatetime, final LocalDateTime toDatetime,
                final boolean status, final LocalDateTime createdOn, final LocalDateTime updatedOn, final String userName, final String userLastName,
                final String userEmail) {
            this.id = id;
            this.assetId = assetId;
            this.userId = userId;
            this.fromDatetime = fromDatetime;
            this.toDatetime = toDatetime;
            this.status = status;
            this.createdOn = createdOn;
            this.updatedOn = updatedOn;
            this.userName = userName;
            this.userLastName = userLastName;
            this.userEmail = userEmail;
        }

    }

    private static final class LocalDateTimeJsonSerializer
            extends JsonSerializer<LocalDateTime> {

        @Override
        public void serialize(final LocalDateTime localDateTime, final JsonGenerator generator, final SerializerProvider serializers)
                throws IOException {
            // Just encode it's as a simple string -- this is all you need
            generator.writeString(localDateTime.toString());
        }

    }

}

"Client"

final class Client {

    private Client() {
    }

    static void consumeResponse(final Reader reader) {
        final List<AssetBookingGsonDto> payload = gson.fromJson(reader, assetBookingListType);
        for ( final AssetBookingGsonDto assetBooking : payload ) {
            System.out.println(assetBooking.assetId + ": " + assetBooking.createdOn);
        }
    }

    // TypeToken.getType() results are constant and can be saved to re-use  
    private static final Type assetBookingListType = new TypeToken<List<AssetBookingGsonDto>>() {
    }.getType();

    // Gson instantiation may take some time, and Gson is thread-safe, so we can re-use it too
    private static final Gson gson = new GsonBuilder()
            // Note that nullSafe() method
            .registerTypeHierarchyAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())
            .create();

    @SuppressWarnings("unused")
    private static final class AssetBookingGsonDto {

        // I prefer not to use getters/setters for DTO data bags
        // * final can be stripped off by Gson -- not a problem
        // * primitive fields cannot be null, but simple 0 and false would cause inlining by javac (0 and false are constaants), so we're cheating javac
        private final int id = Integer.valueOf(0);
        private final int assetId = Integer.valueOf(0);
        private final int userId = Integer.valueOf(0);
        private final LocalDateTime fromDatetime = null;
        private final LocalDateTime toDatetime = null;
        private final boolean status = Boolean.valueOf(false);
        private final LocalDateTime createdOn = null;
        private final LocalDateTime updatedOn = null;
        private final String userName = null;
        private final String userLastName = null;
        private final String userEmail = null;

    }

    private static final class LocalDateTimeAdapter
            extends TypeAdapter<LocalDateTime> {

        @Override
        public void write(final JsonWriter out, final LocalDateTime value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public LocalDateTime read(final JsonReader in)
                throws IOException {
            // Now just decode the string
            return LocalDateTime.parse(in.nextString());
        }

    }

}

例子

public static void main(final String... args)
        throws IOException {
    try ( final Reader reader = new InputStreamReader(produceResponse()) ) {
        consumeResponse(reader);
    }
}

输出:

10: 2017-04-16T17:15:00.000

此外,"pre-custom-serializers"和"custom-serializers"场景的响应(漂亮打印,长度是在漂亮打印之前计算的):

before.json, 656 字节

[
    {
        "id": 1,
        "assetId": 10,
        "userId": 100,
        "fromDatetime": {
            "iLocalMillis": 1492362900000,
            "iChronology": {
                "iBase": {
                    "iBase": null,
                    "iParam": null,
                    "iMinDaysInFirstWeek": 4
                },
                "iParam": null
            }
        },
        "toDatetime": {
            "iLocalMillis": 1492362900000,
            "iChronology": {
                "iBase": {
                    "iBase": null,
                    "iParam": null,
                    "iMinDaysInFirstWeek": 4
                },
                "iParam": null
            }
        },
        "status": true,
        "createdOn": {
            "iLocalMillis": 1492362900000,
            "iChronology": {
                "iBase": {
                    "iBase": null,
                    "iParam": null,
                    "iMinDaysInFirstWeek": 4
                },
                "iParam": null
            }
        },
        "updatedOn": {
            "iLocalMillis": 1492362900000,
            "iChronology": {
                "iBase": {
                    "iBase": null,
                    "iParam": null,
                    "iMinDaysInFirstWeek": 4
                },
                "iParam": null
            }
        },
        "userName": "foo",
        "userLastName": "bar",
        "userEmail": "foo.bar@email"
    }
]

after.json, 272 字节

[
    {
        "id": 1,
        "assetId": 10,
        "userId": 100,
        "fromDatetime": "2017-04-16T17:15:00.000",
        "toDatetime": "2017-04-16T17:15:00.000",
        "status": true,
        "createdOn": "2017-04-16T17:15:00.000",
        "updatedOn": "2017-04-16T17:15:00.000",
        "userName": "foo",
        "userLastName": "bar",
        "userEmail": "foo.bar@email"
    }
]

非常自我描述。