【发布时间】:2019-10-05 19:56:39
【问题描述】:
我有一个简单的要求,如果应用程序遇到异常,我的 JAX-RS Rest 端点应该返回一个带有 500 HTTP 标头状态的自定义 JSON 响应。
构建响应所需的数据来自具有多个属性的对象(见下文)。问题是,我只对每个属性的一个或两个值感兴趣(几十个)。而且我无法修改这些模型/类中的任何一个(有些具有用于 JSON 处理的 Jackson 注释,例如,在序列化期间应丢弃空属性)。
public class MainObject {
private FirstProperty firstProperty;
private SecondProperty secondProperty;
private ThirdProperty thirdProperty;
// other codes
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
public class FirstProperty {
private boolean bol = true;
private double dob = 5.0;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class SecondProperty {
private String str;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class ThirdProperty {
private int intProp = true;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
我应该看到返回的预期 JSON 在客户端(例如,浏览器 -- 在 Edge 中测试):
{
"firstProperty" : { "subProperty" : [ "val1" ] },
"secondProperty" : { "str" : "val2", "subproperty" : [ "val3", "val6" ] },
"thirdProperty" : { "subProperty" : [ "val4" ] }
}
相反,我所有的字段名称和它们的值都被转义了它们的引号,并且整个值周围都有额外的双引号,例如:
{
"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }",
"secondProperty" : "{ \"str\" : \"val2\", \"subproperty\" : [ \"val3\", \"val6\" ] }",
"thirdProperty" : "{ \"subProperty\" : [ \"val4\" ] }"
}
请注意花括号前后的额外"。我的环境是:
Java 1.8.45
FasterXML Jackson 2.9.8
Spring Boot 2.0.1
RestEasy (JBoss) JAX-RS
JBoss 6.4
我消除了代码中的大部分“噪音”,看看在什么时候会发生这种情况。这是控制器:
@Path("/")
public class MainController {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/rest/path")
public MainObject getMainObject throws MyCustomException {
// A service call that throws MyCustomException
}
}
还有 JAX-RS ExceptionMapper 我将响应发回:
@Provider
public class MyCustomExceptionMapper extends ExceptionMapper<MyCustomException> {
@Override
public Response toResponse(MyCustomException ex) {
HashMap<String, Object> responseBody = new HashMap<>();
String strEx = ex.getStrEx(); // Comes from SecondProperty.str stored in MyCustomException, not that it matters
// Instantiate an empty object that contains
MainObject obj = new MainObject();
obj.getFirstProperty().setSubProperty(ex.getStrs());
obj.getSecondProperty().setStr(strEx);
obj.getSecondProperty().setSubProperty(ex.getStrs());
obj.getThirdProperty().setSubProperty(ex.getStrs());
responseBody.put("firstProperty", serializeFirstProperty(obj.getFirstProperty()));
responseBody.put("secondProperty", serializeSecondProperty(obj.getSecondProperty()));
responseBody.put("thirdProperty", serializeThirdProperty(obj.getThirdProperty()));
Response response = Response.status(/* 500 status */).entity(responseBody).build();
return response;
}
}
由于我只需要从我的每个类型中发回一个很小的整体属性子集,我创建了一个自定义StdSerializer,它只会填充所需的属性。为简洁起见,我只做serializeFirstProperty(),但它们或多或少都相同:
private StdSerializer<FirstProperty> getFPSerializer(FirstProperty firstProperty) {
return new StdSerializer<FirstProperty>(FirstProperty.class) {
@Override
public void serialize(FirstProperty value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
if (/* there are items in FirstProperty.subProperty */) {
gen.writeArrayFieldStart("subProperty");
for (String str : value.getSubProperty()) {
gen.writeString(str);
}
gen.writeEndArray();
}
gen.writeEndObject();
}
}
private <T> ObjectMapper getCustomOM(StdSerializer<?> serializer) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addSerializer(serializer);
mapper.registerModule(module);
return mapper;
}
然后使用这些辅助方法,例如:
private String serializeFirstProperty(FirstProperty firstProperty) {
ObjectMapper mapper = getCustomOM(getFPSerializer(firstProperty));
String ser = null;
try { ser = mapper.writeValueAsString(firstProperty); }
catch (JsonProcessingException e) { return null; }
return ser;
}
我用ObjectMapper 尝试了无数种配置,例如disable(JsonParser.Feature.ALLOW_BACKLASH_ESCAPING_ANY_CHARACTER)(找不到任何与 JsonGenerator 相关的标志,我真的想以类似的方式禁用它)。
或者从serializeFirstProperty()显式返回Object,或者在返回ser时将serializeFirstProperty()中的所有\"替换为"。
或设置自定义 StdSerializer 的 JsonGenerator.setCharacterEscapes(new CharacterEscapes() { //... } 或玩弄 JAX-RS Response 无济于事。我似乎总是得到一个带引号的“字符串”值,例如:
"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }"
如果我只是这样做
responseBody.put("firstProperty", mapper.writeValueAsString(obj.getFirstProperty()));
这会以某种方式产生正确的 JSON 输出,但是,它包含许多在此异常处理案例中我不想要的不必要的属性。
有趣的是,当我凝视response(或responseBody 地图)时,一切看起来都是正确的(我没有看到带有双引号的值)。
还请注意,我不仅不能修改模型,而且它们的一些属性在创建过程中使用默认值进行实例化,因此非空包含不起作用,如果我这样做,它们将出现在最终的 JSON 中不要使用自定义序列化。
有谁知道是什么导致了这个转义和额外的引号?
【问题讨论】:
标签: java json jax-rs resteasy jackson2