Skip to content

Actuator is using different methods for serialization and deserialization #23410

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

Closed
mkarasik opened this issue Sep 18, 2020 · 5 comments
Closed

Comments

@mkarasik
Copy link

Actuator APIs (Logger for example) are using

Map deserializer when JSON is received and ObjectMapper when it is send. Which causes mismatched messages.

In my case I want ObectMapper to be configured with snake case and keep Maps unchanged.
If I do this I have to use lower camel notation on my POST/PUT methods and snake case in GETs.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Sep 18, 2020
@wilkinsona
Copy link
Member

Thanks for the report, but I'm not sure that I have understood the problem. The Actuator uses an ObjectMapper instance for JSON serialisation and deserialisation. Are you observing a difference when mapping a POJO vs a Map, perhaps? To remove any doubt, can you please provide a minimal example that reproduces the problem? You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Sep 18, 2020
@mkarasik
Copy link
Author

mkarasik commented Sep 18, 2020

Sorry for not clear explanation. I am not too familiar with Spring terminology. I have the following ObjectMapper configuration in my app

 WebMvcConfigurer

@Override
 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        for (var converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                var objectMapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
                objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
            }
        }
    }

It is probably not perfect code but that is what I have.

After doing this I found out that actuator Logger endpoint returns 'snake case' JSONs in GET requests but still require lower case camel notation in POST.

I spent some time in debugger and found that somewhere deep inside Spring code it is using Map deserializer to extract request parameters.

It is coming from

ServletWebOperationAdapter

@Override
public Object handle(HttpServletRequest request,
	@RequestBody(required = false) Map<String, String> body) {

you can see that body is already presented as Map here, so further processing in ReflectiveOperationInvoker.resolveArguments can't resolve arguments if they are sent in snake case.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Sep 18, 2020
@wilkinsona
Copy link
Member

This is Jackson's standard behaviour. It treats Map and POJO deserialisation differently and you have only configured its POJO deserialisation behaviour. If you want to customise the map key deserialisation, you'll have to configure Jackson with a custom key deserializer to convert the received snake_case into the required camelCase. You can do so with a SimpleModule that's exposed as a bean:

@Bean
Module customKeyDeserializer() {
	SimpleModule module = new SimpleModule();
	module.addKeyDeserializer(String.class, new KeyDeserializer() {

		@Override
		public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
			StringBuilder result = new StringBuilder();
			char previous = 0;
			for (char c : key.toCharArray()) {
				if (c != '_') {
					result.append(previous == '_' ? Character.toUpperCase(c) : c);
				}
				previous = c;
			}
			return result.toString();
		}

	});
	return module;
}

@wilkinsona
Copy link
Member

See also #22950 for a somewhat similar problem with the serialisation of error responses and #20291 for providing an Actuator-specific ObjectMapper.

I'm going to close this one. Generally speaking we don't recommend try to customise the form of the JSON that the actuator accepts and produces. That's not easy right now as the ObjectMapper is shared with the application. #20291 will address that. In the meantime, the approach shown above should be used if you want to change the format of keys that the actuator consumes.

@wilkinsona wilkinsona removed status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged labels Sep 21, 2020
@mkarasik
Copy link
Author

Yes, I configured it differently and I want them to be different.

My complain is that actuator APIs are processed asymmetric: input is de-serialized through Map and output through POJO.
I can try to configure custom map deserializer for Actuator endpoints only (not sure if this is possible at all) but I still think this is actuator design problem, not my configuration issue.

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