首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在将json反序列化为object时,使用jackson将asp.net / MS专有json with格式转换为java8 LocalDateTime

在将json反序列化为object时,使用jackson将asp.net / MS专有json with格式转换为java8 LocalDateTime
EN

Stack Overflow用户
提问于 2017-06-08 18:09:47
回答 2查看 349关注 0票数 4

我从Spring Boot App调用App服务,使用jackson-jsr-310作为能够使用LocalDateTime的maven依赖项

代码语言:javascript
复制
RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = this.createHeaders();
ResponseEntity<String> response;
response  = restTemplate.exchange(uri,HttpMethod.GET,new HttpEntity<Object>(httpHeaders),String.class);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
BusinessPartner test = mapper.readValue(response.getBody(), BusinessPartner.class);

我的问题是在最后一行,代码产生了这个错误:

java.time.format.DateTimeParseException:未能在索引0处分析文本'/Date(591321600000)/‘

response.getBody()中生成的JSON如下所示:

代码语言:javascript
复制
{  
    "d":{  
        ...
        "Address":{...},
        "FirstName":"asd",
        "LastName":"asd",
        "BirthDate":"\/Date(591321600000)\/",
    }
}

在我的模型类中,我有以下成员:

代码语言:javascript
复制
@JsonProperty("BirthDate")
private LocalDateTime birthDate;

所以,在这里搜索了一下后,我发现这个/Date(...)/似乎是微软专有的here格式,默认情况下,Jackson不能将其反序列化为对象。

有些问题建议创建一个自定义的SimpleDateFormat并将其应用于对象映射器,这是我试图做到的,但是我认为我错过了mapper.setDateFormat(new SimpleDateFormat("..."));的正确语法

我试过了,比如mapper.setDateFormat(new SimpleDateFormat("/Date(S)/"));

或者在最后甚至是mapper.setDateFormat(new SimpleDateFormat("SSSSSSSSSSSS)"));

但这似乎也不起作用,所以我现在没有想法,希望这里的一些人能帮助我。

edit 1:

进一步研究,似乎有一种方法是为杰克逊编写一个定制的DateDeSerializer。所以我试了一下:

代码语言:javascript
复制
@Component
public class JsonDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

private DateTimeFormatter formatter;

private JsonDateTimeDeserializer() {
    this(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}

public JsonDateTimeDeserializer(DateTimeFormatter formatter) {
    this.formatter = formatter;
}

@Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
    if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
        String unixEpochString = parser.getText().trim();
        unixEpochString = unixEpochString.replaceAll("[^\\d.]", "");

        long unixTime = Long.valueOf(unixEpochString);
        if (unixEpochString.length() == 0) {
            return null;
        }

        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
        localDateTime.format(formatter);

        return localDateTime;
    }
    return null;
}

}

它实际上返回了我想要的内容,在模型中使用

代码语言:javascript
复制
@JsonDeserialize(using = JsonDateTimeDeserializer.class)

但并不完全是这样:这段代码返回一个值:1988-09-27T01:00。但是在第三方系统中,xml值是1988-09-27T00:00:00

很明显,这里的ZoneId:

代码语言:javascript
复制
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());

是问题所在,除了错误的日期格式。

那么这里有没有人能帮我把time-part转换成总是使用0,并让我的日期格式正确呢?那就太好了!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-06-08 21:07:54

我假设数字591321600000是纪元毫秒(从1970-01-01T00:00:00Z开始的毫秒数)。

如果是这样的话,我认为SimpleDateFormat不能帮助您(至少我找不到使用这个类解析来自纪元千年表的日期的方法)。模式S (根据javadoc)用于格式化或解析时间的毫秒字段(因此其最大值为999),不适用于您的情况。

我能让它工作的唯一方法就是创建一个自定义的反序列化程序。

首先,我创建了这个类:

代码语言:javascript
复制
public class SimpleDateTest {

    @JsonProperty("BirthDate")
    private LocalDateTime birthDate;

    // getter and setter
}

然后,我创建了自定义反序列化程序,并将其添加到自定义模块中:

代码语言:javascript
复制
// I'll explain all the details below
public class CustomDateDeserializer extends JsonDeserializer<LocalDateTime> {

    @Override
    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String s = p.getText(); // s is "/Date(591321600000)/"

        // assuming the format is always /Date(number)/
        long millis = Long.parseLong(s.replaceAll("\\/Date\\((\\d+)\\)\\/", "$1"));

        Instant instant = Instant.ofEpochMilli(millis); // 1988-09-27T00:00:00Z

        // instant is in UTC (no timezone assigned to it)
        // to get the local datetime, you must provide a timezone
        // I'm just using system's default, but you must use whatever timezone your system uses
        return instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
    }
}

public class CustomDateModule extends SimpleModule {

    public CustomDateModule() {
        addDeserializer(LocalDateTime.class, new CustomDateDeserializer());
    }
}

然后,我将这个模块添加到我的映射器中,它起作用了:

代码语言:javascript
复制
// using reduced JSON with only the relevant field
String json = "{ \"BirthDate\": \"\\/Date(591321600000)\\/\" }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// add my custom module
mapper.registerModule(new CustomDateModule());

SimpleDateTest value = mapper.readValue(json, SimpleDateTest.class);
System.out.println(value.getBirthDate()); // 1988-09-26T21:00

下面是关于反序列化方法的一些评论。

首先,我将millis 591321600000转换为Instant (一个表示协调世界时瞬间的类)。millis中的591321600000等同于1988-09-27T00:00:00Z

但这是UTC日期/时间。要获取本地日期和时间,您必须知道您所在的时区,因为在每个时区中都有不同的日期和时间(世界上的每个人都在同一时刻,但他们的本地日期/时间可能不同,这取决于他们所在的位置)。

在我的示例中,我只使用了ZoneId.systemDefault(),它获取系统的默认时区。但是,如果您不想依赖默认值,而是希望使用特定的时区,请使用ZoneId.of("timezone name")方法(您可以使用ZoneId.getAvailableZoneIds()获取所有可用时区名称的列表-此方法返回ZoneId.of()方法接受的所有有效名称)。

由于我的默认时区为America/Sao_Paulo,因此此代码将birthDate设置为1988-09-26T21:00

如果您不想转换为特定的时区,可以使用ZoneOffset.UTC。因此,在反序列化方法中,最后一行将是:

代码语言:javascript
复制
   return instant.atZone(ZoneOffset.UTC).toLocalDateTime();

现在本地日期将是1988-09-27T00:00 -因为我们使用的是UTC偏移,没有时区转换,本地日期/时间也没有改变。

PS:如果您需要将birthDate转换回MS的自定义格式,您可以编写自定义序列化程序并添加到自定义模块中。要将LocalDateTime转换为该格式,可以执行以下操作:

代码语言:javascript
复制
LocalDateTime birthDate = value.getBirthDate();
// you must know in what zone you are to convert it to epoch milli (using default as an example)
Instant instant = birthDate.atZone(ZoneId.systemDefault()).toInstant();
String msFormat = "/Date(" + instant.toEpochMilli() + ")/";
System.out.println(msFormat); // /Date(591321600000)/

请注意,要将LocalDateTime转换为Instant,您必须知道您所在的时区。在这种情况下,我建议使用相同的时区进行序列化和反序列化(在本例中,您可以使用ZoneOffset.UTC而不是ZoneId.systemDefault()

票数 4
EN

Stack Overflow用户

发布于 2018-09-27 03:27:32

下面是我编写的一些Groovy代码,它们也可以处理时区偏移:https://gist.github.com/jeffsheets/938733963c03208afd74927fb6130884

代码语言:javascript
复制
class JsonDotNetLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

    @Override
    LocalDateTime deserialize(JsonParser parser, DeserializationContext ctxt) {
        convertDotNetDateToJava(parser.text.trim())
    }

    /**
     * Returns a Java LocalDateTime when given a .Net Date String
     * /Date(1535491858840-0500)/
     */
    static LocalDateTime convertDotNetDateToJava(String dotNetDate) {
        // Strip the prefix and suffix to just 1535491858840-0500
        String epochAndOffset = dotNetDate[6..-3]

        // 1535491858840
        String epoch = epochAndOffset[0..-6]

        // -0500 Note, keep the negative/positive indicator
        String offset = epochAndOffset[-5..-1]
        ZoneId zoneId = ZoneId.of("UTC${offset}")

        LocalDateTime.ofInstant(Instant.ofEpochMilli(epoch.toLong()), zoneId)
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44432789

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档