Skip to content

@JacksonInject added to property overrides value from the JSON even if useInput is OptBoolean.TRUE #2678

@ptanov

Description

@ptanov

Hello,
I have problem when using @JacksonInject on final field that is initialized using constructor (see the whole example at the end of the message).

The problem is that the correct value from JSON is used when the constructor is called, see the stacktrace:

AnnotatedConstructor.call(Object[]) line: 124	
StdValueInstantiator.createFromObjectWith(DeserializationContext, Object[]) line: 283	
StdValueInstantiator(ValueInstantiator).createFromObjectWith(DeserializationContext, SettableBeanProperty[], PropertyValueBuffer) line: 229	
PropertyBasedCreator.build(DeserializationContext, PropertyValueBuffer) line: 195	
BeanDeserializer._deserializeUsingPropertyBased(JsonParser, DeserializationContext) line: 422	
BeanDeserializer(BeanDeserializerBase).deserializeFromObjectUsingNonDefault(JsonParser, DeserializationContext) line: 1287	
BeanDeserializer.deserializeFromObject(JsonParser, DeserializationContext) line: 326	
BeanDeserializer.deserialize(JsonParser, DeserializationContext) line: 159	
ObjectMapper._readMapAndClose(JsonParser, JavaType) line: 4013	
ObjectMapper.readValue(String, Class<T>) line: 3004	
JacksonInjectTest.testReadValueInjectables()

But later this value is overrided by the default value of the injectable (but useInput is OptBoolean.TRUE) because _injectables != null at BeanDeserializer.deserialize(JsonParser, DeserializationContext, Object) line: 216, full stacktrace:

AnnotatedField.setValue(Object, Object) line: 105	
ValueInjector.inject(DeserializationContext, Object) line: 51	
BeanDeserializer(BeanDeserializerBase).injectValues(DeserializationContext, Object) line: 1520	
BeanDeserializer.deserialize(JsonParser, DeserializationContext, Object) line: 217	
BeanDeserializer._deserializeUsingPropertyBased(JsonParser, DeserializationContext) line: 441	
BeanDeserializer(BeanDeserializerBase).deserializeFromObjectUsingNonDefault(JsonParser, DeserializationContext) line: 1287	
BeanDeserializer.deserializeFromObject(JsonParser, DeserializationContext) line: 326	
BeanDeserializer.deserialize(JsonParser, DeserializationContext) line: 159	
ObjectMapper._readMapAndClose(JsonParser, JavaType) line: 4013	
ObjectMapper.readValue(String, Class<T>) line: 3004	
JacksonInjectTest.testReadValueInjectables()	

This happens even if I set mapper.configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, false); which is weired.

Here is the whole example:

package com.drooms.semantic.flink.jobs;

import static java.util.Objects.requireNonNull;
import static org.testng.Assert.assertEquals;

import java.io.IOException;

import org.testng.annotations.Test;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.OptBoolean;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonInjectTest {
	protected static class Some {
		private final String field1;

		@JacksonInject(value = "defaultValueForField2", useInput = OptBoolean.TRUE)
		private final String field2;

		public Some(@JsonProperty("field1") final String field1,
				@JsonProperty("field2") @JacksonInject(value = "defaultValueForField2",
						useInput = OptBoolean.TRUE) final String field2) {
			this.field1 = requireNonNull(field1);
			this.field2 = requireNonNull(field2);
		}

		public String getField1() {
			return field1;
		}

		public String getField2() {
			return field2;
		}
	}

	@Test
	public void testReadValueInjectables() throws JsonParseException, JsonMappingException, IOException {
		final ObjectMapper mapper = new ObjectMapper();
		final InjectableValues injectableValues =
				new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
		mapper.setInjectableValues(injectableValues);

		final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
		assertEquals(actualValueMissing.getField1(), "field1value");
		assertEquals(actualValueMissing.getField2(), "somedefaultValue");

		final Some actualValuePresent =
				mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
		assertEquals(actualValuePresent.getField1(), "field1value");
		assertEquals(actualValuePresent.getField2(), "somedefaultValue");
		// if I comment @JacksonInject that is next to the property the valid assert is the correct one:
		// assertEquals(actualValuePresent.getField2(), "field2value");
	}
}

In my original case I'm using lombok to generate the constructor and the annotation is copied from property to the constructor parameter (you can check it here: projectlombok/lombok#1528 (comment) ). You can check https://stackoverflow.com/a/60987798/2054634 for more information on the context of the problem.

P.S. I'm using jackson v. 2.9.6

Thanks in advance,
Plamen

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.20Issues planned at 2.20 or laterhas-failing-testIndicates that there exists a test case (under `failing/`) to reproduce the issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions