java之无法在 Springboot "no single-String constructor/factory method"中使用 Jackson 反序列化自定义日期对象

grandyang 阅读:47 2023-09-06 19:40:43 评论:0

编辑:

为了澄清这个问题,我制作了一个图表:

我一直在迁移自定义 Jetty Web 服务以使用 Spring REST 模板,但我遇到了以下反序列化程序的问题。

自定义日期格式返回如下:"2016-10-18" 我需要在调用此自定义Date 的构造函数之前将其转换为 3 个整数> 输入。

以前这是可行的,因为我们只是向服务传递了一个注册了反序列化器的 ObjectMapper 并且它调用了 deserialize 没有任何问题。

但是现在我收到一个错误,因为 Jackson 试图在我的自定义 Date 类型上调用一个不存在的 String 构造函数。这个库不是由我维护的,因此我不能添加这个构造函数。

错误:

"Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not instantiate value of type [simple type, class com.intercorp.domain.date.Date] from String value ('2016-09-30'); no single-String constructor/factory method

注意事项

注释我尝试序列化的属性有效,但我不认为这是一个解决方案,因为注册反序列化器应该检测类型并调用自身。 与切换到注释相比,我更感兴趣的是为什么这不起作用:

@JsonDeserialize(using=JacksonConfig.CustomDateDeserializer.class) 
private Date paymentDate; 

我有以下 Spring 配置:

@Import({JacksonConfig.class}) 
public class WebServiceConfig { 
 
    @Autowired 
    private MappingJackson2HttpMessageConverter jacksonConverter; 
 
    @Bean 
    public MyWebService MyWebService () { 
        RestTemplate restTemplate = new RestTemplate(); 
        restTemplate.getMessageConverters().add(jacksonConverter); 
        return new MyWebService(restTemplate); 
    } 
} 

JacksonConfig.class

@Configuration 
public class JacksonConfig { 
 
    @Bean 
    public MappingJackson2HttpMessageConverter jacksonConverter() { 
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); 
        converter.setObjectMapper(objectMapper()); 
        return converter; 
    } 
 
    @Bean 
    @Primary 
    public ObjectMapper objectMapper() { 
        Jackson2ObjectMapperBuilder mapperBuilder = new Jackson2ObjectMapperBuilder(); 
        mapperBuilder.modules(myModule()).timeZone(TimeZone.getTimeZone("UTC")) 
                     .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 
        return mapperBuilder.build(); 
    } 
 
    @Bean 
    public Module myModule() { 
        SimpleModule module = new SimpleModule("myModule", new Version(0, 0, 1, null)); 
 
        module.addSerializer(new CustomDateSerializer()); 
        //not Java Date - custom Date... 
        module.addDeserializer(Date.class, new CustomDateDeserializer()); 
 
        return module; 
    } 
 
    // breakpoints indicate this is never called 
    static public class CustomDateDeserializer extends JsonDeserializer<Date> { 
 
            @Override 
            @SneakyThrows 
            public Date deserialize(JsonParser jp, DeserializationContext ctxt) { 
                String text = jp.getText(); 
                if (text == null) { 
                    return null; 
                } 
                if (text.length() >= 10) { 
                    int year = parseInt(text.substring(0, 4)); 
                    int month = parseInt(text.substring(5, 7)); 
                    int day = parseInt(text.substring(8, 10)); 
                    return new Date(year, month, day); 
                } 
                // deserialize json object 
                else if (JsonToken.START_OBJECT.equals(jp.getCurrentToken())) { 
                    JsonNode dateNode = jp.getCodec().readTree(jp); 
                    return getDate(dateNode); 
                } 
                else { 
                    return new Date(Integer.parseInt(text)); 
                } 
            } 
        } 
 
} 

支付状态.java

包 com.otpp.memberapi.service.iaccess.data.pension;

import com.intercorp.domain.date.Date; 
import com.intercorp.domain.money.Money; 
import lombok.AllArgsConstructor; 
import lombok.Data; 
import lombok.NoArgsConstructor; 
 
@Data 
@AllArgsConstructor 
@NoArgsConstructor 
public class PensionPaymentStatus { 
 
    private boolean available; 
    private Money netMonthly; 
    //this date fails to deserialise 
    private Date paymentDate; 
    private boolean suspended; 
 
} 

示例 JSON 负载

{ 
    "available": true, 
    "netMonthly": "1000.00", 
    "paymentDate": "2016-10-19", 
    "suspended": false 
} 

日期类很大,只是包装了 Joda 时间(在 Java8 之前的环境中使用)。它有几个构造函数,我尝试使用的是 YYYY、MM、DD 整数构造函数。

请您参考如下方法:

您的转换器似乎没有反序列化请求。

RestTemplate 的无参数构造函数使用默认转换器对其进行初始化,相关片段:

.... 
if (jackson2Present) { 
    this.messageConverters.add(new MappingJackson2HttpMessageConverter()); 
} 
.... 

并且由于它使用“先胜一筹”策略来找到合适的转换器,因此它永远不会使用您的转换器,因为它位于列表的后面。

你需要改变:

RestTemplate restTemplate = new RestTemplate(); 
restTemplate.getMessageConverters().add(jacksonConverter); 

像这样:

RestTemplate restTemplate = new RestTemplate(Collections.singletonList(jacksonConverter)); 

取决于您是否需要额外的转换器。


标签:Spring Boot
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号