Jackson JSON 基于条件的日期格式序列化

Jackson JSON date format serialization based on condition

我正在使用 Mule 3.5.2,并且我有一个发送和接收 JSON 消息的 REST 服务。该服务适用于挪威和瑞典。所有日期都以字符串形式发送,但瑞典和挪威的格式不同。我通过 URL 知道哪个国家呼叫我们的服务。我正在使用自定义日期序列化器和反序列化器。

我可以在收到 JSON 消息时作弊,格式差异很大,在我的自定义反序列化器中我可以尝试一种格式。如果失败了,我就试试另一个。但是:如何以正确的格式序列化?

似乎没有任何方法可以将参数发送到序列化程序,该特定消息将发送到挪威,因此请使用此日期格式...接下来发送到瑞典使用另一种格式等。

我拥有的代码,可能会有所帮助:

@GET
@Path("{country:se|no}/{id}")
public Response webservice(@PathParam("country") String country,
        @PathParam("id") String id) {
    country = country.toUpperCase();
    WebServiceResponse response = doWebServiceStuff(id, country)
    return Response.ok(reponse).build();
}

Response 有一个 .language() 方法,但这似乎只影响 headers。

@JsonAutoDetect
public class WebServiceResponse {

    @JsonSerialize(using = JsonDateSerializer.class)
    @JsonDeserialize(using = JsonDateDeserializer.class)
    private Date date;

    public void setDate(Date d) { this.date = d; }
    public Date getDate() { return this.date; }
}

今天的序列化程序。我希望它能够适应挪威用户或瑞典用户。

public class JsonDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
        String formattedDate = dateFormat.format(date);
        gen.writeString(formattedDate);
        provider.getConfig().getDateFormat()
    }
}

解串器。它有同样的问题,但我可以用 try/catch 包围它...如果瑞典日期格式无效,请尝试使用挪威数字解析并抛出 RuntimeException 如果它仍然是一个问题。

public class JsonDateDeserializer extends JsonDeserializer<Date> {

    @Override
    public Date deserialize(JsonParser parser, DeserializationContext deserializationContext)
        throws IOException, JsonProcessingException {

        String dateText = parser.getText();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        try {
            return dateFormat.parse(dateText);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            throw new RuntimeException("Can't parse date " + dateText, e);
        }
    }

}

顺便说一句...我正在使用 Jackson 的 codehaus 版本,因为它似乎可以与 Mule 一起使用。我试过 FasterXML-version 但它没有使用我的自定义序列化器,也没有使用新的基于注解的格式化程序(所以你不需要自定义序列化器)。确切地说是版本 1.9.11。

同样:问题是,我如何根据每条消息的 URL(更确切地说是从外部)的条件来控制日期格式。我在 webservice-method(第一个代码块)中知道我正在与哪个国家/地区交谈,但在序列化器中不知道...

结果

我在下面提供的解决方案确实可以解决我的问题,但我相信它不可能在 Mule 3.5.2 EE 中运行。但是,如果使用 Mule 3.6.0 或 3.7.0(这似乎是现在的最新版本),这可能是适合您以及其他可能使用其他框架的人的解决方案。

评论中没有提到,但我确实尝试注释掉 "String country = uriInfo.getPathParameters().getFirst("country");"并将国家/地区硬编码为 "no",我确实获得了挪威日期格式。当用 "se" 重新编译它时,我确实得到了瑞典语格式,所以这个解决方案确实有效,即使我无法让它工作。

更新2

我确实与 Mule Support 进行了讨论。在 3.5.x 的 Mule 和更早的版本中,jersey-json 和 jackson-jaxrs 被运送并且它加载有点随机(并且取决于不同的环境)。可以从 $MULE_HOME/lib/opt 中删除 jersey-json。 3.6.x及以后将只发货jackson-jaxrs.

因为我坐在一个有很多工作流程的系统上,所以我没有时间测试删除 jersey-json 是否不会破坏任何东西(因为删除文件会影响所有流程,而不仅仅是这个流程).基本上 3.6.x 及更高版本将更好地控制 Jersey(选择提供者等),并使它能够正常工作。

"...how can I control the date format based on conditions from for instance the URL (more exactly from the outside) for each message"

虽然需要多做一些工作,但一种方法是为每种类型的请求创建不同的 ObjectMapper 配置。为了确定将使用哪一个,我们可以在 ContextResolver. We could inject a UriInfo 中做出决定,进入解析器,以获得 @PathParam("country") 的值。然后据此做出决定,将使用哪个映射器。例如

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
    
    private final ObjectMapper sweMapper;
    private final ObjectMapper norMapper;
    private final ObjectMapper defaultMapper;
    
    @Context
    private UriInfo uriInfo;
    
    public ObjectMapperContextResolver() {
        defaultMapper = new ObjectMapper();
        
        sweMapper = new ObjectMapper();
        SimpleModule sweModule = new SimpleModule("SweModule", new Version(1,0,0,null));
        sweModule.addDeserializer(Date.class, new JsonDateDeserializer(sweFormat));
        sweModule.addSerializer(Date.class, new JsonDateSerializer(sweFormat));
        sweMapper.registerModule(sweModule);
        
        norMapper = new ObjectMapper();
        SimpleModule norModule = new SimpleModule("NorModule", new Version(1,0,0,null));
        norModule.addDeserializer(Date.class, new JsonDateDeserializer(norFormat));
        norModule.addSerializer(Date.class, new JsonDateSerializer(norFormat));
        norMapper.registerModule(norModule);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        String country = uriInfo.getPathParameters().getFirst("country");
        if (country == null) {
            return defaultMapper;
        }
        
        switch (country) {
            case "se": return sweMapper;
            case "no": return norMapper;
            default: return defaultMapper;
        }
    }  
}

我们使用三个映射器的原因之一是,它们的创建成本很高。其次,配置它们不是线程安全的。由于 ContextResolver 将是一个单例,因此只有一个映射器将用于该应用程序。所以我们只为不同的情况创建三个。

如果你走这条路,你还应该记得从字段中删除序列化注释。


更新

因此对于 Jersey 2.6,上述解决方案似乎存在问题。它只是在启动时失败。我能够找到的解决方案是 使用此依赖项

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-json</artifactId>
    <version>${jersey-version}</version>
</dependency>

似乎加载此模块的某些部分导致它失败。相反,只需使用 Pure Jackson 依赖项(上面实际上引入并使用它自己)。

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.13</version>
</dependency>

注: jersey-json:1.6使用上述依赖的1.7.1。我刚切换到使用最新的 1.x 版本。所以你可能想也可能不想把它切换回来。

摆脱你可能拥有的旧神器,即

<init-param>
    <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
    <param-value>true</param-value>
</init-param>

并将 Jackson 包添加为要扫描的包

<init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>
        com.your.packages,
        org.codehaus.jackson.jaxrs
    </param-value>
</init-param>

或者,如果您正在使用一些 Mule 特定配置,只需注册这些

  • org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider
  • org.codehaus.jackson.jaxrs.JacksonMappingExceptionMapper
  • org.codehaus.jackson.jaxrs.JacksonParseExceptionMapper