代码之家  ›  专栏  ›  技术社区  ›  Kannan Ekanath

Jackson序列化以报告无法序列化的字段列表

  •  0
  • Kannan Ekanath  · 技术社区  · 9 年前

    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"]}
    

    类似拦截器的东西可能适合我,但如果有人有一些代码示例,那就太好了!

    2 回复  |  直到 9 年前
        1
  •  1
  •   toongeorges    9 年前

    这段代码不能保证最后调用“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));
        }
    }
    
        2
  •  0
  •   Kannan Ekanath    9 年前

    我使用了上面@Toongerges建议的变体。下面是一个实用类,它将“异常安全”转换为JSON。返回的JSON中会有一个额外的元素,名为“exceptionMessages”,其中包含JSON序列化失败的属性(如果不是Java bean属性,则包含方法名)。如果这种风格更适合您,可以更改它以返回一对JsonNode,一个用于对象,另一个用于异常消息

    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())));
        }
      }
    }