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

When deserialize call arg constructor or default constructor Specification changed? or bug? #2008

Closed
LichKing-lee opened this issue Apr 23, 2018 · 4 comments
Milestone

Comments

@LichKing-lee
Copy link
Contributor

LichKing-lee commented Apr 23, 2018

Hello.

sample code.

public class JacksonTest {
	private ObjectMapper objectMapper;

	@BeforeEach
	public void setUp() {
		this.objectMapper = new ObjectMapper();
	}

	@Test
	public void jacksonTest() throws IOException {
		String json = "{\"jacksonAge\":30,\"jacksonName\":\"changyong\"}";

		Person actual = this.objectMapper.readValue(json, Person.class);
		String result = this.objectMapper.writeValueAsString(actual);
                String expected = "{\"jacksonAge\":40,\"jacksonName\":\"leechangyong\"}";

		assertEquals(expected, result);
	}

	public static class Person {
		@JsonIgnore
		private String etc;
		private int age;
		private String name;

		public Person(){}

		@ConstructorProperties({"etc", "age", "name"})
		public Person(String etc, int age, String name) {
			this.etc = etc;
			this.age = age;
			this.name = name;
		}

		@JsonGetter("jacksonAge")
		public int getAge() {
			return age;
		}

		@JsonSetter("jacksonAge")
		public void reviseAge(int age) {
			this.age = age + 10;
		}

		@JsonGetter("jacksonName")
		public String getName() {
			return name;
		}

		@JsonSetter("jacksonName")
		public void reviseName(String name) {
			this.name = "lee" + name;
		}

		public String getEtc() {
			return this.etc;
		}

		@Override
		public String toString() {
			return "Person{" +
				"age=" + age +
				", name='" + name + '\'' +
				'}';
		}
	}
}

have @JsonIgnore annotation field
When deserialize I want call @JsonSetter after default constructor.
It was until 2.9.1
But didn't call @JsonSetter from 2.9.2
It specification changed? or bug?

Thank you

@LichKing-lee LichKing-lee changed the title Specification change? or bug? Specification changed? or bug? Apr 23, 2018
@LichKing-lee LichKing-lee changed the title Specification changed? or bug? @JsonSetter Specification changed? or bug? Apr 23, 2018
@LichKing-lee LichKing-lee changed the title @JsonSetter Specification changed? or bug? When deserialize call arg constructor or default constructor Specification changed? or bug? Apr 23, 2018
@cowtowncoder
Copy link
Member

Specification has not changed, and setters, if any, should be called after constructor is called.

@LichKing-lee
Copy link
Contributor Author

LichKing-lee commented Apr 24, 2018

This test do success until 2.9.1
But test do fail from 2.9.2

2.9.1 be call default constructor and setter
but 2.9.2 be call arg constructor

@cowtowncoder
Copy link
Member

I am not sure I understand what the code is intended to do, but to me behavior in 2.9.5 seems the way it should be.

But here is how logic is intended to work:

  1. After resolving all logical properties and creators, properties found to match creator properties are passed to that creator, and nothing else. If values are missing, null (or equivalent for primitives) is passed for those properties
  2. If any other properties are found, they are passed through mutators (setter, or field, that matches name)

The complexity in this case comes from various renaming annotations, which probably causes confusion of what should happen.

Starting with @ConstructorProperties, it is equivalent to @JsonCreator(mode = PROPERTIES), as well as "implicit" naming of parameters -- same way as how Java 8 allows parameter names to be found, and how getter names (like getName has implicit name of "name"). Since they are implicit names, renaming is allowed: they are not explicit overrides.

From this we get implicit property names "etc", "name" and "age".

In addition, there are fields and getters with matching implicit name. These get associated with constructor parameters. But getters also have explicit annotations (name overrides), which will be used to rename associated accessors, including constructor parameters.
This is how we get to properties named "jacksonAge" and "jacksonName" (and no more "age" or "name").

Finally, there are also setters, with explicit names "jacksonAge" and "jacksonName".
So we have 3 properties:

  • "jacksonName" (with getter, setter, field and constructor parameter)
  • "jacksonAge" (same)
  • "etc" (with getter, field and constructor parameter)

Since constructor parameter has the highest precedence, on deserialization, values get passed through it. No setter is called, no field assigned, since all properties are matched.

Now: I don't know why behavior in 2.9.0 and 2.9.1 would behave differently. None of the fixes listed in 2.9.2 seem related. I do know there have been fixes to matching of creator parameters with other property accessors over time, but not between these versions.

As to intended behavior: I would need to understand what exactly you are trying to achieve, and we can probably get that to work with different annotations.

For example, to prevent renaming, you can replace use of @ConstructorProperties with explicit names on parameters using @JsonProperty. So like:

@JsonCreator
  public Person2008(@JsonProperty("etc") String etc,
        @JsonProperty("age") int age, @JsonProperty("name") String name) {  }

which would prevent these from being renamed due to naming match with getter/setter/field.

@cowtowncoder
Copy link
Member

cowtowncoder commented May 24, 2018

Also: quite probably related to:

FasterXML/jackson-modules-java8#67

although not the same thing. Just likely related due to timing, same area of code (creator handling).

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

No branches or pull requests

2 participants