Jackson serialization to report list of fields that could not be serialized
我正在用一些 REST/jackson 功能package遗留代码。特别是假设我有一个名为 LegacyObject
的接口
1 2 3 4 5 6 |
实现是一个遗留类,假设不能更改。我的 REST 服务有一个将 LegacyObject 转换为 JSON 的端点。唯一的问题是,只要其中一个 getter 抛出异常,这种转换就会完全失败。我需要的是一个像下面这样的json(假设getAge(),getDesc()工作正常,但getName()抛出运行时异常)
1 | {"age": 40,"desc":"some description","unsupportedFields": ["name"]} |
基本上是一种捕获所有序列化失败的字段然后在最后报告的方法。
类似拦截器的东西可能对我有用,但如果有人有一些代码示例,那就太好了!
由于接口中有200个方法,下面用Proxies解决。
此代码不保证最后调用 "getUnsupportedFields" 方法(因此之后仍可能发生一些异常)
1 2 3 4 5 6 |
1 2 3 4 5 | import java.util.List; public interface ExtendedLegacyObject extends LegacyObject { List<String> getUnsupportedFields(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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; } } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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,一个用于对象,一个用于异常消息,如果该样式更适合您
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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()))); } } } |