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

The json conversion to the entity object data binding part is unsuccessful. #2327

Closed
mdpwpdw opened this issue May 14, 2019 · 7 comments
Closed

Comments

@mdpwpdw
Copy link

mdpwpdw commented May 14, 2019

version: 2.9.4

// entity example:
@getter
@Setter
public class User{
private String tDate;
private String tEst;
private String ttttttDate;
}

// test example
@test
public void testObjToJson() throws JsonProcessingException
{
ObjectMapper mapper = new ObjectMapper();
User user = new User();
user.setTDate("wfrefreg");
user.setTEst("dwefref");
String mapJakcson = mapper.writeValueAsString(user);

    System.out.println(mapJakcson);

}

result:
{"ttttttDate":"xxxx","tdate":"wfrefreg","test":"dwefref"}
The actual result wants to be:
{"ttttttDate":"xxxx","tDate":"wfrefreg","tEst":"dwefref"}

After debugging the source code, I found
com.fasterxml.jackson.databind.util.BeanUtil.legacyManglePropertyName

protected static String legacyManglePropertyName(final String basename, final int offset)
{
.......
for (; i < end; ++i) {
c = basename.charAt(i);
d = Character.toLowerCase(c);
if (c == d) {
sb.append(basename, i, end);
break;
}
// This place should be wrong.
// Should be modified into sb.append(c);

sb.append(d);
}
}

@cowtowncoder
Copy link
Member

Huh? I think what I need here is FULL Java object declaration -- all "@getter" and "@Setter" annotations (Lombok?) expanded, and then explain why you think something is wrong.

And also see if enabling MapperFeature.USE_STD_BEAN_NAMING does what you want.
It looks like naming convention you use is bit unusual.

@mdpwpdw
Copy link
Author

mdpwpdw commented May 15, 2019

Is a getter, setter method generated by lombook, because the getter generated by lombook, setter method and eclipse generated getter, setter method name is a little different, but I look at the source code of jackson found that jackson in the generation of json entity attribute name is through setter The method name interception operation obtained, there is a case difference in the generated json attribute name. I tested alibaba fastjson, google gson, generated json, whether it is gelipse generated getter, setter, or lombook generated getter , setter , generated json is the same, but jackson is different in the case of two kinds of getter, setter

Since fastjson is the default json parsing framework for spring, if the following example appears, the backend will receive less data.

for example :
`import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;

@getter
@Setter
public class LUser implements Serializable
{
private static final long serialVersionUID = 2524357019179494943L;

private String tEst;

private String tDate;

private String ttttttDate;

}`

`import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/json")
public class JsonController
{
/**
* test
* @param user
* @return
*/
@PostMapping("/test")
public LUser test(@requestbody LUser user)
{
// user Some fields have not been passed over
return user;
}
}`

If I send a post request to the test method, the requested data json is
{
      "tEst": "aa",
      "tDate": "bbbb",
      "ttttttDate": "cccc"
}

If I send a post request to the test method, the requested data json is
{
      "tEst": "aa",
      "tDate": "bbbb",
      "ttttttDate": "cccc"
}

The background did not receive tEst, tDate data, ttttttDate data received normally, I also tried to set the parameter MapperFeature.USE_STD_BEAN_NAMING, but it is not useful, if I set the spring json converter to alibaba fastjson, google gson, all data can be Normal reception

@mxmlnglt
Copy link

First, it seems the getters/setters generated by Eclipse (menu Source>Generate Getters and Setters...) are a bit awkward for tDate and tEst. It seems it's due (bug or feature) to Eclipse not handling properly the only 1st lowercase character.

	public class User {
		private String tDate;
		private String tEst;
		private String ttttttDate;
		public String gettDate() {
			return tDate;
		}
		public void settDate(String tDate) {
			this.tDate = tDate;
		}
		public String gettEst() {
			return tEst;
		}
		public void settEst(String tEst) {
			this.tEst = tEst;
		}
		public String getTtttttDate() {
			return ttttttDate;
		}
		public void setTtttttDate(String ttttttDate) {
			this.ttttttDate = ttttttDate;
		}
	}

@mxmlnglt
Copy link

mxmlnglt commented May 28, 2019

@hj819748949 @cowtowncoder

So if you "fix" the wrong getters and setters as:

	public class User {
		private String tDate;
		private String tEst;
		private String ttttttDate;
		public String getTDate() {
			return tDate;
		}
		public void setTDate(String tDate) {
			this.tDate = tDate;
		}
		public String getTEst() {
			return tEst;
		}
		public void setTEst(String tEst) {
			this.tEst = tEst;
		}
		public String getTtttttDate() {
			return ttttttDate;
		}
		public void setTtttttDate(String ttttttDate) {
			this.ttttttDate = ttttttDate;
		}
	}

Then with this setup:

		ObjectMapper mapper = new ObjectMapper();
		User user = new User();
		user.setTDate("wfrefreg");
		user.setTEst("dwefref");
		String mapJakcson = mapper.writeValueAsString(user);
		System.out.println(mapJakcson);

you get: {"ttttttDate":null,"test":"dwefref","tdate":"wfrefreg"} (wrong test and tdate here)

By adding:

		mapper.enable(MapperFeature.USE_STD_BEAN_NAMING);

you get: {"ttttttDate":null,"TEst":"dwefref","TDate":"wfrefreg"} (wrong TEst and TDate here)

@cowtowncoder
Copy link
Member

Ok. As far as I can see, the result is as expected in both cases. The problem in matching stems from discrepancy between field names and setters/getters: the way Jackson works in figuring out implied property names is:

  1. Field name is used exactly as is
  2. Method names are extracted from methods with 2 variations, old, slightly bean non-compliant (always lower-case all leading upper-case letters), newer bean compliant (only lower-case a single leading upper case letter followed by one or more lower-case letters)

and then applying possible annotations, and finally combining all accessors by derived names.

In this case getter/setter names do not result in matches. This is the way it is, and it looks like Lombok is using bit different algorithm in deriving getter/setter names from fields, using what seems like a reasonable algorithm but producing irreversible names -- so from

getTEst()

it is impossible to uniquely get back to tEst without handling unification at an earlier phase, possibly in case-insensitive manner.
This is probably why Eclipse generated gettEst() as that would actually get properly mapped to field tEst.

Unfortunately I do not think I can change anything in Jackson to easily handle this case at least for Jackson 2.x. But even further, handling of case-insensitive names reliably is a difficult undertaking (and possibly incurring significant overhead for runtime) since after rewriting name unification to do case-insensitive matching (to get to full set of accessors) it would then be necessary to either:

  1. Figure out which of implied logical property names (not accessor names) is the "correct" one to get ("tEst" or "TEst"?), OR
  2. Force case-insensitive match on input to allow multiple matches -- possibly everything, or maybe more likely one of 2 (or 3) candidates. Latter might actually work with "Alias" feature (added in 2.9), although single name would need to be selected for serialization. For that, getter name would have priority, so we would get "TEst".

So. There is no trivial fix and none will be added for 2.x. For 3.x I could consider improvement to do name-insensitive matching during property introspection.

@mxmlnglt
Copy link

mxmlnglt commented Jun 5, 2019

I'm sorry, but I don't understand the first part about the "discrepancy", and the way Jackson works.

Further reading about led me to this issue: FasterXML/jackson-module-kotlin#92 which points to an answer on StackOverflow explaining the "problem": https://stackoverflow.com/a/30207335 (+ original blog post: http://futuretask.blogspot.com/2005/01/java-tip-6-dont-capitalize-first-two.html )

Seems like it's rather a strange coding of the java.beans.Introspector.decapitalize() method, but I didn't understand how this would impact Jackson itself.

Afterall, as noticed in this comment https://stackoverflow.com/questions/2948083/naming-convention-for-getters-setters-in-java#comment34902684_16146215 under another StackOverflow question about the same matter, the Introspector method is designed to:

[...] to take a string and convert it to normal Java variable name capitalization.

see Javadoc: https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize-java.lang.String-

Not the other way round (i.e. taking a variable name and convert it to standard getter/setter <PropertyName>).

@cowtowncoder
Copy link
Member

I am sorry but I can't explain in more detail. The way Jackson determines names to use starts -- and must start -- from combination of method/field names, and match them (not knowing which one would be "correct"), whereas Lombok starts with specific field name and converts to method.
Jackson's use of decapitalize is controlled by already mentioned MapperFeature.USE_STD_BEAN_NAMING: when that is enabled, behavior is fully Java Bean convention compliant; when disabled, case of multiple leading upper-case characters deviates.

But at this point there is no simple change to Jackson that would help here: to resolve this particular issue, case-insensitive reconciliation would need to be implemented.

I have no plans to do that.

So you will either need to change naming convention used for fields, or add annotations to make sure fields/methods match.

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

3 participants