Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected ValueInstantiationException if register ParameterNamesModule #157

Closed
quaff opened this issue Nov 14, 2019 · 12 comments
Closed

Unexpected ValueInstantiationException if register ParameterNamesModule #157

quaff opened this issue Nov 14, 2019 · 12 comments

Comments

@quaff
Copy link

quaff commented Nov 14, 2019

I'm deserialize COSE key, every thing works fine with version 2.10.1, but failed with ParameterNamesModule registered since I want object immutable, I'm not sure this is same as #123.
here is simple test project pname.zip

@quaff
Copy link
Author

quaff commented Nov 15, 2019

I minimize the test, It caused by add method getValue on enum EllipticCurve , If the annotation @Getter removed the test will pass.

package test;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;

import lombok.Getter;
import lombok.Value;

public class DeserializeTest {

	@Test
	public void test() throws Exception {
		EC2Key key = new ObjectMapper().registerModule(new ParameterNamesModule()).readValue("{\"-1\":1}",
				EC2Key.class);
		assertEquals(EllipticCurve.secp256r1, key.getCurve());
	}

	@Value
	public static class EC2Key {

		@JsonProperty("-1")
		private final EllipticCurve curve;

		@JsonCreator
		public EC2Key(@JsonProperty("-1") EllipticCurve curve) {
			this.curve = curve;
		}

	}

	@Getter
	public enum EllipticCurve {

		secp256r1(1), secp384r1(2), secp521r1(3);

		private final int value;

		private EllipticCurve(int value) {
			this.value = value;
		}

		@JsonValue
		public int toValue() {
			return value;
		}

		@JsonCreator
		public static EllipticCurve fromValue(int value) {
			for (EllipticCurve e : values())
				if (e.value == value)
					return e;
			return null;
		}

	}

}

@kupci
Copy link
Member

kupci commented Nov 15, 2019

_

If the annotation @getter removed the test will pass.

_

So it looks like if you don't use Lombok it is working?

import lombok.Getter;
import lombok.Value;

Since this doesn't appear to be related to the jackson-modules-java8, it might be better if moved to jackson-databind, where there are other Lombok-related issues.
https://github.com/FasterXML/jackson-databind/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+lombok

@cowtowncoder
Copy link
Member

Also note that test must NOT use Lombok: it needs to reproduce it with code as it would be after Lombok has done its processing.

However: since parameter-names module is the reason for triggering, I think issue should stay here.

@quaff
Copy link
Author

quaff commented Nov 16, 2019

It's nothing about lombok, It's caused by @JsonValue annotated on toValue and getValue added, workaround is move @JsonValue to getValue.

		public int getValue() {
			return this.value;
		}

		@JsonValue
		public int toValue() {
			return this.value;
		}

@cowtowncoder
Copy link
Member

That is good (that is not Lombok-specific), what I meant is simply that the reproduction should be complete without reference.

@cowtowncoder
Copy link
Member

I can not reproduce the issue: modified test passes as expected with 2.10.1

@quaff
Copy link
Author

quaff commented Nov 16, 2019

Please try this

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>test</groupId>
	<artifactId>pname</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<jackson.version>2.10.1</jackson.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.version}</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.module</groupId>
			<artifactId>jackson-module-parameter-names</artifactId>
			<version>${jackson.version}</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>
package test;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;

public class DeserializeTest {

	@Test
	public void test() throws Exception {
		EC2Key key = new ObjectMapper().registerModule(new ParameterNamesModule()).readValue("{\"-1\":1}",
				EC2Key.class);
		assertEquals(EllipticCurve.secp256r1, key.getCurve());
	}

	public static class EC2Key {

		@JsonProperty("-1")
		private final EllipticCurve curve;

		public EllipticCurve getCurve() {
			return curve;
		}

		@JsonCreator
		public EC2Key(@JsonProperty("-1") EllipticCurve curve) {
			this.curve = curve;
		}

	}

	public enum EllipticCurve {

		secp256r1(1), secp384r1(2), secp521r1(3);

		private final int value;

		private EllipticCurve(int value) {
			this.value = value;
		}

		public int getValue() {
			return this.value;
		}

		@JsonValue
		public int toValue() {
			return this.value;
		}

		@JsonCreator
		public static EllipticCurve fromValue(int value) {
			for (EllipticCurve e : values())
				if (e.value == value)
					return e;
			return null;
		}

	}

}

@quaff
Copy link
Author

quaff commented Nov 18, 2019

I find that the test only failed with eclipse (v4.13.0 verified) compiler.

com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `test.DeserializeTest$EllipticCurve`, problem: argument type mismatch
 at [Source: (String)"{"-1":1}"; line: 1, column: 7] (through reference chain: test.DeserializeTest$EC2Key["-1"])
	at com.fasterxml.jackson.databind.exc.ValueInstantiationException.from(ValueInstantiationException.java:47)
	at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1732)
	at com.fasterxml.jackson.databind.DeserializationContext.handleInstantiationProblem(DeserializationContext.java:1106)
	at com.fasterxml.jackson.databind.deser.std.FactoryBasedEnumDeserializer.deserialize(FactoryBasedEnumDeserializer.java:146)
	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:530)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:528)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:417)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1287)
	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:4202)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
	at test.DeserializeTest.test(DeserializeTest.java:17)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	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.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Caused by: java.lang.IllegalArgumentException: argument type mismatch
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.fasterxml.jackson.databind.introspect.AnnotatedMethod.callOnWith(AnnotatedMethod.java:122)
	at com.fasterxml.jackson.databind.deser.std.FactoryBasedEnumDeserializer.deserialize(FactoryBasedEnumDeserializer.java:138)
	... 33 more

https://github.com/FasterXML/jackson-databind/blob/2.10/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java#L138
the variable value became String "-1" instead of Integer(1) if ParameterNamesModule registered.

@cowtowncoder
Copy link
Member

One key question is whether parameter name information is (or is not) included. But I would recommend using this:

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public EC2Key(@JsonProperty("-1") EllipticCurve curve) {

in case of 1-argument creator method, as well as

	@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
	public static EllipticCurve fromValue(int value) {

to ensure that proper interpretation is used.

@quaff
Copy link
Author

quaff commented Nov 19, 2019

(mode = JsonCreator.Mode.DELEGATING) on fromValue(int value) could fix this no matter whether (mode = JsonCreator.Mode.PROPERTIES) present, I doesn't understand why 1-argument creator is especial, and in this example why Integer became String.

@cowtowncoder
Copy link
Member

@quaff 1-argument creator is special because there are 2 different ways to understand it, and there is no way to reliably determine what user might mean. Basically:

@JsonCreator
public Value(int a) {
}

could either match JSON Integer value like

42

and if so, would be delegated: first bound to int, and that directly passed to constructor

OR it could match JSON Object with one property, a:

{ "a" : 42 }

Things get more complicated if a was a more complex type.

The reason other constructors are not problematic is that "delegating" version can not be considered as there are multiple values.

In case of 1-argument case with no explicit mode, Jackson tries to use heuristics to figure out more likely choice: this takes into account whether constructor/factory method parameter has name or not (either via @JsonProperty or, if available on Java 8 later, parameter name), and if there is known property with that name (because of get-method). This is still just a guess and could be wrong; especially if @JsonProperty is not used.

This does not necessarily explain all confusion (for example, int vs String), but hopefully helps with one part.

@cowtowncoder
Copy link
Member

@quaff One more note, as per your earlier mention:

It's nothing about lombok, It's caused by @JsonValue annotated on toValue and getValue added, workaround is move @JsonValue to getValue.

That actually explains one thing: if you have getValue() that infers existence of property named "value", and strongly hits that your intent would be to use "Properties" based deserialization (since Creator method also has "value" argument).

At this point I think solution is that you should indicate mode to use for 1-argument Creator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants