我有以下类(从我实际上想做的事情中简化了这些类,但是例外是相同的):
ParameterNameTest.java
package foo;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
public class ParameterNameTest {
@Test
public void test() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
Settings settings = objectMapper.readValue("{\"ignoreDate\": true}", DateSettings.class);
}
@Test
public void testConstructorParameters() {
Constructor[] constructors = DateSettings.class.getConstructors();
for (Constructor constructor : constructors) {
System.out.print(constructor.getName() + "( ");
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter : parameters) {
System.out.print(parameter.getType().getName() + " " + parameter.getName() + " ");
}
System.out.println(")");
}
}
}
DateSettings.java
package foo;
public class DateSettings implements Settings {
private final boolean ignoreDate;
public DateSettings(boolean ignoreDate) {
this.ignoreDate = ignoreDate;
}
public boolean isIgnoreDate() {
return ignoreDate;
}
}
Settings.java
package foo;
public interface Settings {}
通过阅读Stackoverflow上的Jackson文档和其他文章,我的理解是,这应该允许我将JSON对象反序列化为DateSettings类,而无需注释该类(我不能这样做,因为在我的实际情况下是第三方图书馆)。但是,出现以下异常:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `foo.DateSettings` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"ignoreDates": true}"; line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1032)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at foo.ParameterNameTest.test(ParameterNameTest.java:18)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
我知道编译时需要启用-parameters
,并且使用上面的testConstructorParameters
测试使我确信这不是问题。
我还能在哪里出错?
更新:这似乎是由于此问题https://github.com/FasterXML/jackson-databind/issues/1498引起的,该问题已经开放了一段时间。如果有人有解决方法,我很想看看。
答案 0 :(得分:1)
所以我今天过得很慢,这是一个有趣的问题。我在github杰克逊页面上发现了相同的问题,而且看起来确实是回归。我发现有一种解决方法,虽然有点黑,但保留了向后兼容性(希望如此)并解决了您的直接问题。由于package-privacy,我需要复制一些类。如果将所有课程都放在同一个jackson包中,则可能可以解决它。这是我的完整解决方案(具有正确的依赖关系,您应该能够粘贴并运行该解决方案):
package com.paandadb.test;
import java.io.IOException;
import java.lang.reflect.Executable;
import java.lang.reflect.MalformedParametersException;
import java.lang.reflect.Parameter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
public class Test {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new HackedParameterModule(JsonCreator.Mode.PROPERTIES));
DateSettings settings = objectMapper.readValue("{\"ignoreDate\": false}", DateSettings.class);
System.out.println(settings.ignoreDate);
}
public static class DateSettings {
private final boolean ignoreDate;
public DateSettings(boolean ignoreDate) {
this.ignoreDate = ignoreDate;
}
public boolean isIgnoreDate() {
return ignoreDate;
}
}
// we need to hack this because `ParameterExtractor` is package private, so we can't extend this class
public static class HackedAnnotationIntrospector extends ParameterNamesAnnotationIntrospector {
private static final long serialVersionUID = 1L;
HackedAnnotationIntrospector(Mode creatorBinding, ParameterExtractor parameterExtractor) {
super(creatorBinding, parameterExtractor);
}
}
// we need to register a different introspector for the annotations
public static class HackedParameterModule extends ParameterNamesModule {
private static final long serialVersionUID = 1L;
public HackedParameterModule(Mode properties) {
super(properties);
}
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new ParameterNamesAnnotationIntrospector(JsonCreator.Mode.DEFAULT, new ParameterExtractor()));
}
}
// This is the hacked introspector that simply returns default instead of null
// you may want to make more checks here to make sure it works the way you want to and has no
// side effects. Same thing here - need to extend because of package private `ParameterExtractor`
public static class ParameterNamesAnnotationIntrospector extends NopAnnotationIntrospector {
private static final long serialVersionUID = 1L;
private final JsonCreator.Mode creatorBinding;
private final ParameterExtractor parameterExtractor;
ParameterNamesAnnotationIntrospector(JsonCreator.Mode creatorBinding, ParameterExtractor parameterExtractor)
{
this.creatorBinding = creatorBinding;
this.parameterExtractor = parameterExtractor;
}
@Override
public String findImplicitPropertyName(AnnotatedMember m) {
if (m instanceof AnnotatedParameter) {
return findParameterName((AnnotatedParameter) m);
}
return null;
}
private String findParameterName(AnnotatedParameter annotatedParameter) {
Parameter[] params;
try {
params = getParameters(annotatedParameter.getOwner());
} catch (MalformedParametersException e) {
return null;
}
Parameter p = params[annotatedParameter.getIndex()];
return p.isNamePresent() ? p.getName() : null;
}
private Parameter[] getParameters(AnnotatedWithParams owner) {
if (owner instanceof AnnotatedConstructor) {
return parameterExtractor.getParameters(((AnnotatedConstructor) owner).getAnnotated());
}
if (owner instanceof AnnotatedMethod) {
return parameterExtractor.getParameters(((AnnotatedMethod) owner).getAnnotated());
}
return null;
}
/*
/**********************************************************
/* Creator information handling
/**********************************************************
*/
@Override
public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
JsonCreator ann = _findAnnotation(a, JsonCreator.class);
// THIS IS THE FIXING BIT
// Note: I only enable this for your specific class, all other cases are handled in default manner
Class<?> rawType = a.getRawType();
if(ann == null && rawType.isAssignableFrom(DateSettings.class)) {
return JsonCreator.Mode.DEFAULT;
}
if (ann != null) {
JsonCreator.Mode mode = ann.mode();
// but keep in mind that there may be explicit default for this module
if ((creatorBinding != null)
&& (mode == JsonCreator.Mode.DEFAULT)) {
mode = creatorBinding;
}
return mode;
}
return null;
}
// I left the other functions from the original code in to prevent breakage
@Override
@Deprecated // remove AFTER 2.9
public JsonCreator.Mode findCreatorBinding(Annotated a) {
JsonCreator ann = _findAnnotation(a, JsonCreator.class);
if (ann != null) {
JsonCreator.Mode mode = ann.mode();
if ((creatorBinding != null)
&& (mode == JsonCreator.Mode.DEFAULT)) {
mode = creatorBinding;
}
return mode;
}
return creatorBinding;
}
@Override
@Deprecated // since 2.9
public boolean hasCreatorAnnotation(Annotated a)
{
// 02-Mar-2017, tatu: Copied from base AnnotationIntrospector
JsonCreator ann = _findAnnotation(a, JsonCreator.class);
if (ann != null) {
return (ann.mode() != JsonCreator.Mode.DISABLED);
}
return false;
}
}
// This is the package private class that does not allow for proper extending
// which is why we had to copy a bunch of code
public static class ParameterExtractor {
public Parameter[] getParameters(Executable executable) {
return executable.getParameters();
}
}
}
我在代码中留下了很多注释,但在这里将进行详细介绍:
首先,您的问题位于ParameterNamesAnnotationIntrospector#findCreatorAnnotation
中。不能使此类仅查找未注释的构造函数。使用注释调试代码可以发现,对构造函数进行注释只会导致默认的JsonCreator.Mode
函数。这意味着,如果我们希望代码能够正常工作,则需要它来识别不存在的默认值。但是首先,我们需要获得自己的模块。因此,我们这样做:
public static class HackedParameterModule extends ParameterNamesModule {
private static final long serialVersionUID = 1L;
public HackedParameterModule(Mode properties) {
super(properties);
}
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new ParameterNamesAnnotationIntrospector(JsonCreator.Mode.DEFAULT, new ParameterExtractor()));
}
}
这将注册一个扩展ParameterNamesModule
的自定义模块。仅因为我们需要注册自定义ParameterNamesAnnotationIntrospector
才需要它。那个使用了ParameterExtractor
类,它是包私有的,因此我们需要逐步了解层次结构。
点击并注册,我们将进入内省。在这里,我添加了以下代码:
@Override
public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
JsonCreator ann = _findAnnotation(a, JsonCreator.class);
// THIS IS THE FIXING BIT
// Note: I only enable this for your specific class, all other cases are handled in default manner
Class<?> rawType = a.getRawType();
if(ann == null && rawType.isAssignableFrom(DateSettings.class)) {
return JsonCreator.Mode.DEFAULT;
}
if (ann != null) {
JsonCreator.Mode mode = ann.mode();
// but keep in mind that there may be explicit default for this module
if ((creatorBinding != null)
&& (mode == JsonCreator.Mode.DEFAULT)) {
mode = creatorBinding;
}
return mode;
}
return null;
}
此代码只是添加了新的默认行为。如果未找到注释,并且最终将导致异常,并且尝试创建的类原始类型为DateSettings
,则我们仅返回默认模式,这是我们需要启用杰克逊的模式使用1个可用的构造函数。注意:如果您的类具有多个构造函数,这很可能会中断,但我没有尝试过
所有这些都已注册,我可以运行我的主要功能,而不会出现错误并打印出正确的值:
DateSettings settings = objectMapper.readValue("{\"ignoreDate\": false}", DateSettings.class);
System.out.println(settings.ignoreDate);
settings = objectMapper.readValue("{\"ignoreDate\": true}", DateSettings.class);
System.out.println(settings.ignoreDate);
打印:
false
true
我不确定这是否是一个不错的解决方案。但是,如果您确实陷入困境,并且无法进行其他更改,这是一种自定义杰克逊优势的方法。
希望对您有所帮助!
注意:我保留了相同的名称,可能会给您造成导入问题。可能值得将所有类重命名为更具区别性的东西:)
Artur