У меня есть следующие классы (которые я упростил из того, что я на самом деле пытаюсь сделать, но исключение такое же):
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;
}
}
Настройки.java
package foo;
public interface Settings {}
Из чтения документации Джексона и других сообщений в StackOverflow я понял, что это должно позволить мне десериализовать мой объект 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, которая была открыта уже довольно давно. Если у кого-то есть обходной путь, мне было бы интересно его увидеть.
Извините, это была опечатка - ее исправление (теперь отредактированное выше) не меняет исключение.
Попробуйте пометить свой конструктор DateSettings
как JsonCreator
, что исправило его для меня.
Да, это работает, но, как я упоминал в исходном посте, «без аннотации этого класса (чего я не могу сделать, так как в моем реальном случае это сторонняя библиотека)».
Я все еще изучаю это, но, поскольку вы не можете изменить используемый класс, вы можете расширить его и вместо этого использовать аннотацию в своем расширенном классе. Если я найду решение, я дам вам знать :)
Спасибо, я ценю, что вы посмотрели на это - к сожалению, расширение тоже не вариант (или MixIns), так как я не буду знать классы до времени выполнения. В настоящее время я иду по маршруту пользовательского десериализатора, который будет читать настройки JSON как карту, а затем пытаться сопоставить это с подходящим конструктором, но было бы неплохо, если бы Джексон мог это сделать, поэтому мне не нужно пишу/поддерживаю его сам. К сожалению (см. Обновление выше в исходном сообщении), похоже, что это известная «проблема» для конструкторов с одним параметром.
Тааааак... я разобрался, и у меня работает без аннотации. Это некрасиво, но если хотите, я могу опубликовать свое хакерское решение :)
Да, мне определенно было бы интересно посмотреть, работает ли это для меня!
поэтому у меня был медленный день, и это была интересная проблема. Я нашел те же проблемы на странице github 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
Я не уверен, что это достойное решение. Но если вы действительно застряли и нет других изменений, которые вы можете внести, это а способ настроить Джексона в своих интересах.
Надеюсь, это поможет!
Примечание. Я оставил имена без изменений, что может вызвать у вас проблемы с импортом. Возможно, стоит переименовать все классы во что-то более различимое :)
Артур
ваш параметр
ignoreDate
, а ваш json говоритignoreDates