Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/370.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Jackson序列化到无法序列化的字段的报告列表_Java_Jackson - Fatal编程技术网

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

Java Jackson序列化到无法序列化的字段的报告列表,java,jackson,Java,Jackson,我正在用一些REST/jackson功能包装遗留代码。特别是,假设我有一个名为LegacyObject的接口 interface LegacyObject { Integer getAge(); //could throw UnsupportedOperationException String getDesc(); String getName(); //May throw RuntimeException //about 200+ other methods. }

我正在用一些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)

基本上是一种捕获所有序列化失败的字段并在最后报告的方法


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

因为界面中有200多个方法,下面是一个带有代理的解决方案

此代码不保证最后调用“getUnsupportedFields”方法(因此之后仍可能发生一些异常)




我使用了上面@Toongerges建议的变体。下面是一个实用程序类,它将“异常安全”转换为JSON。返回的JSON中将有一个名为“exceptionMessages”的额外元素,其中包含JSON序列化失败的属性(如果不是JavaBean属性,则包含方法名)。这可以更改为返回一对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())));
    }
  }
}
导入静态java.util.stream.Collectors.toMap;
导入com.fasterxml.jackson.databind.ObjectMapper;
导入com.fasterxml.jackson.databind.node.ObjectNode;
导入java.beans.IntrospectionException;
导入java.beans.Introspector;
导入java.lang.reflect.InvocationHandler;
导入java.lang.reflect.InvocationTargetException;
导入java.lang.reflect.Method;
导入java.lang.reflect.Proxy;
导入java.util.LinkedHashMap;
导入java.util.Map;
导入java.util.stream.stream;
导入org.apache.commons.lang3.exception.ExceptionUtils;
公共抽象类JsonUtils{
私有静态ObjectMapper mapper=新ObjectMapper();
/**
*这仅在转换其方法可能引发异常的对象时有用
*默认情况下,抛出异常的“getName”方法将导致整个过程失败
*但是,使用此方法,这些异常将被忽略,并且会出现错误
*返回的JSON中包含所有失败的“exceptionMessages”元素
*
*仅在使用旧代码时使用。
*/
@抑制警告(“未选中”)
公共静态对象节点例外安全写入(类sourceClazz、U obj、布尔预打印){
GuardedInvocationHandler=新的GuardedInvocationHandler(obj);
U代理对象=(U)代理
.newProxyInstance(sourceClazz.getClassLoader(),新类[]{sourceClazz},处理程序);
ObjectNode originalNode=mapper.convertValue(proxiedObject,ObjectNode.class);
ObjectNode exceptionMessages=mapper.convertValue(handler.getExceptionMessagesForJson(),ObjectNode.class);
put(“exceptionMessages”,exceptionMessages);
返回原始节点;
}
私有静态类GuardedInvocationHandler实现InvocationHandler{
私人最终目标;
private Map exceptionMap=新建LinkedHashMap();
私有映射方法属性名称映射;
私有GuardedInvocationHandler(对象目标){
this.target=目标;
this.methodToPropertyNameMap=methodToPropertyNameMap(target.getClass());
}
私有静态映射方法TopPropertyNameMap(clazz类){
试一试{
返回Stream.of(Introspector.getBeanInfo(clazz.getPropertyDescriptors())
.collect(toMap(d->d.getReadMethod(),d->d.getName());
}捕获(内省异常e){
抛出新的运行时异常(e);
}
}
@凌驾
公共对象调用(对象代理、方法、对象[]args)抛出Throwable{
试一试{
return method.invoke(target,args);
}捕获(调用TargetException e){
exceptionMap.put(方法,e.getTargetException());
返回null;
}捕获(例外e){
例外地图放置(方法e);
返回null;
}
}
公共映射getExceptionMessagesForJson(){
返回exceptionMap.entrySet().stream().collect(
toMap(e->MethodTopPropertyNameMap.getOrDefault(e.getKey(),e.getKey().getName()),
e->ExceptionUtils.getMessage(e.getValue());
}
}
}

为什么访问者
抛出
异常?就像我说的,实现是遗留代码的一部分。在重构之前,我试图证明REST服务是有效的。我不想让完整方法失败,最终将状态500发送到客户端。还有另一种代理方法,如果您有200多个方法,请给我一些时间来制定其他方法。由于有200多个方法,上述方法将是次优的。您提出了一个有趣的代理解决方案,这绝对值得一试!如果有一个完美的Jackson拦截器,那么您似乎可以控制Jackson使用@JsonPropertyOrder序列化属性的顺序,以确保“unsupportedFields”属性排在最后。但是,必须明确定义200+个属性的顺序并不是一个优雅的解决方案,非常感谢。我可以看出您也处理过原语(幸运的是我不必处理它),但处理原语的更好方法是使用Google的
默认值
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));
    }
}
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())));
    }
  }
}