How to set PropertyNamingStrategy for RestTemplate in SpringBoot?

14,065

Solution 1

When creating a RestTemplate you need to set the objectMapper to yours. Also, you should declare your custom ObjectMapper as a @Bean so it is constructed by Spring as a singleton and managed for you. Do the same for the PropertyNamingStrategy, instead of 'newing' it up and declaring the class as static.

public class RestConfig
{
    /**
     * Bean to make jackson automatically convert from
     * camelCase (java) to under_scores (json) in property names
     *
     * @return ObjectMapper that maps from Java camelCase to json under_score names
     */
    @Bean
    public ObjectMapper jacksonObjectMapper()
    {
        return new ObjectMapper().setPropertyNamingStrategy(propertyNamingStrategy());
    }

    @Bean
    public PropertyNamingStrategy propertyNamingStrategy()
    {
        return new UpperCaseUnderscoreStrategy();
    }

    @Bean
    public RestTemplate restTemplate() {
       RestTemplate restTemplate = new RestTemplate();
       List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
       MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
       jsonMessageConverter.setObjectMapper(jacksonObjectMapper());
       messageConverters.add(jsonMessageConverter);
       restTemplate.setMessageConverters(messageConverters); 

       return restTemplate;
    }
}

And your class is in a separate file? It doesn't need to be static.

    /**
     * Property naming strategy that converts both ways between camelCase and under_score
     * property names.
     */
    public static class UpperCaseUnderscoreStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase
    {
        /**
         * Converts camelCase to under_score and
         * visa versa.  The idea is that this
         * name strategy can be used for both
         * marshalling and unmarshaling.
         *
         * For example, "userName" would be converted to
         * "user_name" and conversely "user_name" would
         * be converted to "userName".
         *
         * @param input formatted as camelCase or under_score string
         * @return input converted to opposite format
         */
        @Override
        public String translate(String input)
        {
            if (input == null || input.length() == 0)
            {
                return input; // garbage in, garbage out
            }

            //
            // we always take the first character;
            // this preserves initial underscore
            //
            StringBuilder sb = new StringBuilder();

            final int length = input.length();
            int i = 0;  

            //
            // skip initial underscores
            //
            while ((i < length) && ('_' == input.charAt(i)))
            {
                sb.append(input.charAt(i));
                i += 1;
            }

            while (i < length)
            {
                //
                // find underscores, remove and capitalize next letter
                //
                while ((i < length) && ('_' != input.charAt(i)) && !Character.isUpperCase(input.charAt(i)))
                {
                    sb.append(input.charAt(i));
                    i += 1;
                }

                if(i < length)
                {
                    if('_' == input.charAt(i))
                    {
                        // underscore to uppercase

                        //
                        // skip underscores
                        //
                        while ((i < length) && ('_' == input.charAt(i)))
                        {
                            // skip underscores
                            i += 1;
                        }

                        //
                        // capitalize
                        //
                        if (i < length)
                        {
                            sb.append(Character.toUpperCase(input.charAt(i)));
                            i += 1;
                        }
                    }
                    else // uppercase to unscore + lowercase
                    {
                        sb.append('_');
                        sb.append(Character.toLowerCase(input.charAt(i)));
                        i += 1;
                    }
                }
            }
            return sb.toString();
        }
    }

Solution 2

Just add this annotation above POJO's which u will be sending or receiving in ur request response.

@JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)

P.S. the strategy can be different depending upon the requirement.

Solution 3

A shorter answer is to use the Spring's objectMapper. The benefit is that it shares the same configuration in application.properties. So you can set spring.jackson.property-naming-strategy=SNAKE_CASE or whatever there, and it's consistent across the entire application including RestTemplate. Code as follows.

@Configuration
@RequiredArgsConstructor
public class HTTPConfig {
    public final ObjectMapper objectMapper;  // provided by spring

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder()
            .messageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
            .build();
    }
}

Solution 4

I suggest you use this method. You can also add it as a spring bean.

private RestTemplate getRestTemplate() {

    final RestTemplate restTemplate = new RestTemplate();
    final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
    final MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
    jsonMessageConverter.setObjectMapper(new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE));
    messageConverters.add(jsonMessageConverter);
    restTemplate.setMessageConverters(messageConverters);

    return restTemplate;
}
Share:
14,065
Ezward
Author by

Ezward

SOreadytohelp

Updated on July 01, 2022

Comments

  • Ezward
    Ezward almost 2 years

    I have written a SpringBoot app that consumes a rest api and presents a rest api. My model pojo's have camelCase named properties. The json that the app consumes has under_score property names. The json that the app produces has under_score property names. I would like to use a PropertyNamingStrategy that will do the conversion automatically between Java and json names during marshalling/unmarshalling.

    I have Java config that attempts to set a naming strategy that can handle this;

    /**
     * Configuration for Rest api.
     * <p>
     * Created by emurphy on 2/25/16.
     */
    @Configuration
        public class RestConfig
        {
            /**
             * Bean to make jackson automatically convert from
             * camelCase (java) to under_scores (json) in property names
             *
             * @return ObjectMapper that maps from Java camelCase to json under_score names
             */
            @Bean
            public ObjectMapper jacksonObjectMapper()
            {
                return new ObjectMapper().setPropertyNamingStrategy(new UpperCaseUnderscoreStrategy());
            }
    
            /**
             * Property naming strategy that converts both ways between camelCase and under_score
             * property names.
             */
            public static class UpperCaseUnderscoreStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase
            {
                /**
                 * Converts camelCase to under_score and
                 * visa versa.  The idea is that this
                 * name strategy can be used for both
                 * marshalling and unmarshaling.
                 *
                 * For example, "userName" would be converted to
                 * "user_name" and conversely "user_name" would
                 * be converted to "userName".
                 *
                 * @param input formatted as camelCase or under_score string
                 * @return input converted to opposite format
                 */
                @Override
                public String translate(String input)
                {
                    if (input == null || input.length() == 0)
                    {
                        return input; // garbage in, garbage out
                    }
    
                    //
                    // we always take the first character;
                    // this preserves initial underscore
                    //
                    StringBuilder sb = new StringBuilder();
    
                    final int length = input.length();
                    int i = 0;  
    
                    //
                    // skip initial underscores
                    //
                    while ((i < length) && ('_' == input.charAt(i)))
                    {
                        sb.append(input.charAt(i));
                        i += 1;
                    }
    
                    while (i < length)
                    {
                        //
                        // find underscores, remove and capitalize next letter
                        //
                        while ((i < length) && ('_' != input.charAt(i)) && !Character.isUpperCase(input.charAt(i)))
                        {
                            sb.append(input.charAt(i));
                            i += 1;
                        }
    
                        if(i < length)
                        {
                            if('_' == input.charAt(i))
                            {
                                // underscore to uppercase
    
                                //
                                // skip underscores
                                //
                                while ((i < length) && ('_' == input.charAt(i)))
                                {
                                    // skip underscores
                                    i += 1;
                                }
    
                                //
                                // capitalize
                                //
                                if (i < length)
                                {
                                    sb.append(Character.toUpperCase(input.charAt(i)));
                                    i += 1;
                                }
                            }
                            else // uppercase to unscore + lowercase
                            {
                                sb.append('_');
                                sb.append(Character.toLowerCase(input.charAt(i)));
                                i += 1;
                            }
                        }
                    }
                    return sb.toString();
                }
            }
    

    I can see the naming strategy's translate method getting called when my rest service converts Java pojos to json for the response. However, when I'm consuming a rest api, via RestTemplate, I don't see this get called and my resulting pojos are not correctly intialized from the incoming json; all properties whose name would need translation are null. This has forced me to use @JsonProperty on most properties. I have a lot of properties and a lot of pojos - this is a very inelegant, boilerplate kind of solution that SpringBoot is supposed to help with. Is there a way I can set a PropertyNamingStrategy that RestTemplate will use to convert the incoming json names from under_score to camelCase?

    Thanks for your help.

  • Ezward
    Ezward about 8 years
    Thanks for the reply. I've implemented this but it still does not work. When I remove the @JsonProperty annotations in my pojos, I can still see that only properties with simple names, like 'description', get filled in when I make a rest call. If I set a break point in the instance of UpperCaseUnderscoreStrategy.translate(), it never stops there so it is clear that the rest template is not calling it.
  • Ezward
    Ezward about 8 years
    I can see when debugging that a MappingJackson2HttpMessageConverter is chosen from the list of messageConverters. I've traced further into the ObjectMapper and I can see that the DeserializationConfig returned by ObjectMapper.getDeserializationConfig() has it's _propertyNameStrategy value of null, so whatever we've done so far has not gotten into the deserialization pipeline.
  • Ezward
    Ezward about 8 years
    Ok, I have figure this out. Your answer was very helpful. I found out that my low level code was new'ing up a RestTemplate on each call, so the configured template was ignored. Once I changed to autowire the rest template into my low level, everything worked. Now I see the translate() method being called as expected. Thanks for your help.
  • francisco neto
    francisco neto over 2 years
    Thanks! using the objectMapper new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrat‌​egy.SNAKE_CASE) did the trick for me.