简体   繁体   English

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

[英]Jackson serialization to report list of fields that could not be serialized

I am wrapping legacy code with some REST/jackson capabilities. 我用一些REST / jackson功能包装了旧代码。 In particular let's say I have an interface called LegacyObject 特别是说我有一个名为LegacyObject的接口

interface LegacyObject {
   Integer getAge(); //could throw UnsupportedOperationException
   String getDesc();
   String getName(); //May throw RuntimeException
   //about 200+ other methods.
}

The implementation is a legacy class and assume cannot be changed. 该实现是旧类,并且假定无法更改。 My REST service has an endpoint which converts LegacyObject to JSON. 我的REST服务具有将LegacyObject转换为JSON的终结点。 The only problem being that this conversion fails fully whenever one of the getters throws an exception. 唯一的问题是,只要其中一个getter抛出异常,此转换将完全失败。 What I need is a json like the below (assuming getAge(), getDesc() worked okay but getName() threw runtimeexception) 我需要的是一个像下面这样的json(假设getAge(),getDesc()工作正常,但getName()抛出了runtimeexception)

{"age": 40, "desc": "some description", "unsupportedFields": ["name"]}

Basically a way to capture all fields that failed serialization and then report at the end. 基本上是一种捕获序列化失败的所有字段,然后在最后报告的方法。

An interceptor like thing might work for me but if anyone has some code examples that would be great! 诸如此类的拦截器可能对我有用,但如果有人有一些代码示例,那就太好了!

Since there are 200+ methods in the interface, below a solution with Proxies. 由于该界面中有200多种方法,因此在具有代理的解决方案下方。

This code does not guarantee that the "getUnsupportedFields" method is called last (and thus still some exceptions may occur after) 此代码不保证最后一次调用“ 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));
    }
}

I used a variation of what @toongeorges suggested above. 我使用了上面@toongeorges建议的变体。 Here is a utility class which will do a "exception safe" convert to JSON. 这是一个实用程序类,它将“异常安全”转换为JSON。 There will be an extra element in the returned JSON called "exceptionMessages" which contains the properties that failed json serialisation (or the method name if it is NOT a Java bean property). 返回的JSON中将有一个额外的元素,称为“ exceptionMessages”,其中包含失败进行json序列化的属性(如果不是Java bean属性,则为方法名称)。 This can be changed to return a Pair of JsonNode one for the object and one for exceptionMessages if that style suits you better 可以更改为返回一对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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM