使用jackson将asp.net/MS专有json Dateformat转换为java8 LocalDateTime,同时将json反序列化为对象

使用jackson将asp.net/MS专有json Dateformat转换为java8 LocalDateTime,同时将json反序列化为对象,datetime,spring-boot,java-8,jackson,deserialization,Datetime,Spring Boot,Java 8,Jackson,Deserialization,我从Spring Boot应用程序调用一个Web服务,使用jackson-jsr-310作为maven依赖项,以便能够利用LocalDateTime: RestTemplate restTemplate = new RestTemplate(); HttpHeaders httpHeaders = this.createHeaders(); ResponseEntity<String> response; response = restTemplate.exchange(uri,H

我从Spring Boot应用程序调用一个Web服务,使用jackson-jsr-310作为maven依赖项,以便能够利用
LocalDateTime

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);
在我的模型课上,我有以下成员:

@JsonProperty("BirthDate")
private LocalDateTime birthDate;
所以,在这里搜索了一段时间后,我发现这个
/Date(…)/
似乎是Microsoft专有的Dateformat,Jackson无法按默认值反序列化为对象

有些问题建议创建一个自定义的
SimpleDateFormat
并将其应用于opbject映射器,我曾尝试过这样做,但我认为我错过了
mapper.setDateFormat(新的SimpleDateFormat(“…”)的正确语法

我试过使用例如
mapper.setDateFormat(新的SimpleDateFormat(“/Date/”)

或者在末尾偶数
mapper.setDateFormat(新的SimpleDataFormat(“SSSS”)

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

编辑1:

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

@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;
}
但不完全是: 此代码返回一个
LocalDateTime
值:
1988-09-27T01:00
。 但是在第三方系统中,xmlvalue是
1988-09-27T00:00:00

显而易见,这里的区域ID:

LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
除了错误的日期格式之外,还有一个问题


因此,这里有人能帮助我如何切换到总是使用零作为
时间
部分,并使我的日期格式正确吗?那太好了

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

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

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

首先,我创建了这个类:

public class SimpleDateTest {

    @JsonProperty("BirthDate")
    private LocalDateTime birthDate;

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

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

下面是关于反序列化器方法的一些注释

首先,我将millis
591321600000
转换为一个
瞬间
(一个表示UTC瞬间的类)<代码>591321600000
单位为毫秒等于
1988-09-27T00:00:00Z

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

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

由于我的默认时区是
美国/圣保罗
,此代码将
出生日期设置为
1988-09-26T21:00

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

   return instant.atZone(ZoneOffset.UTC).toLocalDateTime();
现在本地日期将是1988-09-27T00:00
——因为我们使用UTC偏移,所以没有时区转换,本地日期/时间也没有更改


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

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

以下是我编写的一些Groovy代码,它也处理时区偏移:

类JSONDOTTNetLocalDateTimeDeserializer扩展JsonDeserializer{ @凌驾 LocalDateTime反序列化(JsonParser解析器,反序列化上下文ctxt){ convertDotNetDateToJava(parser.text.trim()) } /** *当给定.Net日期字符串时,返回Java LocalDateTime */日期(1535491858840-0500)/ */ 静态LocalDateTime转换器DotNetDateToJava(字符串dotNetDate){ //去掉前缀和后缀仅为1535491858840-0500 字符串epochAndOffset=dotNetDate[6..-3] // 1535491858840 字符串历元=偏移量[0..-6] //-0500注意,保持负/正指示灯 字符串偏移量=偏移量[-5..-1] ZoneId ZoneId=ZoneId.of(“UTC${offset}”) LocalDateTime.ofInstant(Instant.ofEpochMilli(epoch.toLong()),zoneId) } }
591321600000
是纪元毫秒(从1970-01-01T00:00:00Z算起的毫秒)?@Hugo是的,请查看我的编辑以了解更多信息。
System.out.println(ZoneId.systemDefault())的输出是什么?
是欧洲/柏林(这里下雨)偏移量为+01:00,这就是为什么我说问题很明显。不考虑来自圣保罗的人;)事实上,这与我得到的非常接近:)我接受你的答案,因为这实际上解决了95%。如果你能帮我解决编辑中最后的问题,那就太好了!发生时移是因为您将UTC值(瞬间)转换为yo
// 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
   return instant.atZone(ZoneOffset.UTC).toLocalDateTime();
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)/
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)
    }
}