[英]Jackson serialization to report list of fields that could not be serialized
我用一些REST / jackson功能包裝了舊代碼。 特別是說我有一個名為LegacyObject的接口
interface LegacyObject {
Integer getAge(); //could throw UnsupportedOperationException
String getDesc();
String getName(); //May throw RuntimeException
//about 200+ other methods.
}
該實現是舊類,並且假定無法更改。 我的REST服務具有將LegacyObject轉換為JSON的終結點。 唯一的問題是,只要其中一個getter拋出異常,此轉換將完全失敗。 我需要的是一個像下面這樣的json(假設getAge(),getDesc()工作正常,但getName()拋出了runtimeexception)
{"age": 40, "desc": "some description", "unsupportedFields": ["name"]}
基本上是一種捕獲序列化失敗的所有字段,然后在最后報告的方法。
諸如此類的攔截器可能對我有用,但如果有人有一些代碼示例,那就太好了!
由於該界面中有200多種方法,因此在具有代理的解決方案下方。
此代碼不保證最后一次調用“ getUnsupportedFields”方法(因此,此后仍可能發生一些異常)
public interface LegacyObject {
Integer getAge(); //could throw UnsupportedOperationException
String getDesc();
String getName(); //May throw RuntimeException
//about 200+ other methods.
}
import java.util.List;
public interface ExtendedLegacyObject extends LegacyObject {
List<String> getUnsupportedFields();
}
public class ExceptionLegacyObject implements LegacyObject {
@Override
public Integer getAge() {
return 40;
}
@Override
public String getDesc() {
return "some description";
}
@Override
public String getName() {
throw new RuntimeException();
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
public class LegacyObjectHandler implements InvocationHandler {
private static final Logger LOG = Logger.getLogger(LegacyObjectHandler.class);
private final List<String> unsupportedFields = new ArrayList<>();
private final LegacyObject legacyObject;
public LegacyObjectHandler(LegacyObject legacyObject) {
this.legacyObject = legacyObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getUnsupportedFields".equals(method.getName())) {
return unsupportedFields;
} else {
try {
return method.invoke(legacyObject, args);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
LOG.error(cause.getMessage(), cause);
unsupportedFields.add(method.getName());
Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive()) {
if (returnType.isAssignableFrom(boolean.class)) {
return false;
} else if (returnType.isAssignableFrom(byte.class)) {
return (byte) 0;
} else if (returnType.isAssignableFrom(short.class)) {
return (short) 0;
} else if (returnType.isAssignableFrom(int.class)) {
return 0;
} else if (returnType.isAssignableFrom(long.class)) {
return 0L;
} else if (returnType.isAssignableFrom(float.class)) {
return 0F;
} else if (returnType.isAssignableFrom(double.class)) {
return 0D;
} else if (returnType.isAssignableFrom(char.class)) {
return (char) 0;
} else {
return null;
}
} else {
return null;
}
}
}
}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Proxy;
public class JacksonTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
ExceptionLegacyObject exceptionLegacyObject = new ExceptionLegacyObject();
ExtendedLegacyObject proxy = (ExtendedLegacyObject) Proxy.newProxyInstance(
LegacyObject.class.getClassLoader(),
new Class[] { ExtendedLegacyObject.class },
new LegacyObjectHandler(exceptionLegacyObject)
);
System.out.println(mapper.writeValueAsString(proxy));
}
}
我使用了上面@toongeorges建議的變體。 這是一個實用程序類,它將“異常安全”轉換為JSON。 返回的JSON中將有一個額外的元素,稱為“ exceptionMessages”,其中包含失敗進行json序列化的屬性(如果不是Java bean屬性,則為方法名稱)。 可以更改為返回一對JsonNode,一個用於對象,另一個用於exceptionMessages(如果該樣式更適合您)
import static java.util.stream.Collectors.toMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.apache.commons.lang3.exception.ExceptionUtils;
public abstract class JsonUtils {
private static ObjectMapper mapper = new ObjectMapper();
/**
* This is only useful in the context of converting a object whose methods could throw exceptions
* into JSON. By default a "getName" method that throws an exception will fail the whole
* serialization however with this method such exceptions will be swallowed and there will be a
* "exceptionMessages" element in the returned JSON which contains all failures
*
* To be used only when working with legacy code.
*/
@SuppressWarnings("unchecked")
public static <U> ObjectNode exceptionSafeWrite(Class<U> sourceClazz, U obj, boolean prettyPrint) {
GuardedInvocationHandler handler = new GuardedInvocationHandler(obj);
U proxiedObject = (U) Proxy
.newProxyInstance(sourceClazz.getClassLoader(), new Class<?>[]{sourceClazz}, handler);
ObjectNode originalNode = mapper.convertValue(proxiedObject, ObjectNode.class);
ObjectNode exceptionMessages = mapper.convertValue(handler.getExceptionMessagesForJson(), ObjectNode.class);
originalNode.put("exceptionMessages", exceptionMessages);
return originalNode;
}
private static class GuardedInvocationHandler implements InvocationHandler {
private final Object target;
private Map<Method, Throwable> exceptionMap = new LinkedHashMap<>();
private Map<Method, String> methodToPropertyNameMap;
private GuardedInvocationHandler(Object target) {
this.target = target;
this.methodToPropertyNameMap = methodToPropertyNameMap(target.getClass());
}
private static Map<Method, String> methodToPropertyNameMap(Class<?> clazz) {
try {
return Stream.of(Introspector.getBeanInfo(clazz).getPropertyDescriptors())
.collect(toMap(d -> d.getReadMethod(), d -> d.getName()));
} catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
exceptionMap.put(method, e.getTargetException());
return null;
} catch (Exception e) {
exceptionMap.put(method, e);
return null;
}
}
public Map<String, String> getExceptionMessagesForJson() {
return exceptionMap.entrySet().stream().collect(
toMap(e -> methodToPropertyNameMap.getOrDefault(e.getKey(), e.getKey().getName()),
e -> ExceptionUtils.getMessage(e.getValue())));
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.