【问题标题】:Gson: Cannot deserialization Joda Time LocalDateTime value properly in AndroidGson:无法在 Android 中正确反序列化 Joda Time LocalDateTime 值
【发布时间】:2017-04-17 06:41:59
【问题描述】:

我有一个在 Java Web 服务器 (Jersey Jax RS RI 2.13) 上运行的 HTTP REST API,它为我提供了使用 Jackson 序列化的 AssetBooking 对象的 ArrayList。在 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 字符串中是正确的)进行反序列化。

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

资产预订.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;
    }
}

在 Android 端收到的 JSON 字符串(它只有一个对象,但我必须删除日期变量 createdOnupdatedOn,以免超过 StackOverflow 的最大帖子大小):

[
    {
        "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"
}

【问题讨论】:

    标签: java android jackson gson jodatime


    【解决方案1】:

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

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

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

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

    “服务器”

    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());
            }
    
        }
    
    }
    

    “客户”

    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"
        }
    ]
    

    相当自我描述。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-04
      • 1970-01-01
      • 2014-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多