Srikrishnan Suresh
Srikrishnan Suresh

Reputation: 729

Change Json key name dynamically - Jackson

I have a java class like :

class TestJsonClass {
    private String propertyA;
    private String propertyB;
    private String propertyC;
}

Now during runtime i want to give different property names for each of the property, and not a static one using @JsonProperty("sample")

How do I accomplish this? I am using Jackson library ad Spring MVC

Thanks in advance...

Upvotes: 1

Views: 9108

Answers (2)

devaaron
devaaron

Reputation: 101

You could inject a custom PropertyNamingStrategy into the ObjectMapper that's used in deserialization.

Just set fields into the PropertyNamingStrategy at runtime, assuming you can map them to something like the default JsonPropertyName (e.g. propertyA, propertyB, propertyC).

public class MyNamingStrategy extends PropertyNamingStrategy {

    String propertyAName, propertyBName, propertyCName;

    public MyNamingStrategy(String propANm, String propBNm, String propCNm) {
        this.propertyAName = propANm;
        //finish
    }

    @Override
    public String nameForField(MapperConfig<?> config, AnnotatedField field,
            String defaultName) {
        return convert(defaultName);
    }

    @Override
    public String nameForGetterMethod(MapperConfig<?> config,
            AnnotatedMethod method, String defaultName) {
        return convert(defaultName);
    }

    @Override
    public String nameForSetterMethod(MapperConfig<?> config,
            AnnotatedMethod method, String defaultName) {
        return convert(defaultName);
    }

    public String convert(String defaultName ){
        return defaultName.replace("propertyA", propertyAName).replace( //finish
    }

Finally you'd create an instance and inject it at runtime. objectMapper.setNamingStrategy(myNamingStrategyInstance));

See this Cowtowncoder post for more on PropertyNamingStrategy:

Jackson 1.8: custom property naming strategies

Or this documentation:

github.com/FasterXML/jackson-docs/wiki/PropertyNamingStrategy

Upvotes: 2

Bhashit Parikh
Bhashit Parikh

Reputation: 3131

You can make use of Modules for this purpose. This is the easiest solutions to your problem. Here is an example:

A simple class that can carry your property-name-mappings for each request:

public class PropertyNameMapper {
    // The class for which the mappings need to take place.
    public Class<?> classToFilter;
    // The mappings property names. Key would be the existing property name
    // value would be name you want in the ouput.
    public Map<String, String> nameMappings = Collections.emptyMap();

    public PropertyNameMapper(Class<?> classToFilter, Map<String, String> nameMappings) {
        this.classToFilter = classToFilter;
        this.nameMappings = nameMappings;
    }
}

A custom BeanPropertyWriter that will be used for specifying the output name for the properties.

public class MyBeanPropertyWriter extends BeanPropertyWriter {
    // We would just use the copy-constructor rather than modifying the
    // protected properties. This is more in line with the current design
    // of the BeanSerializerModifier class (according to its documentation).
    protected MyBeanPropertyWriter(BeanPropertyWriter base, String targetName) {
        super(base, new SerializedString(targetName));
    }

}

Now, a custom BeanSerializerModifier that is called each time to allow you to modify the serialized properties.

public class MySerializerModifier extends BeanSerializerModifier {

    public List<BeanPropertyWriter> changeProperties(
            SerializationConfig config, BeanDescription beanDesc,
            List<BeanPropertyWriter> beanProperties) {

        List<PropertyNameMapper> propertyMappings = getNameMappingsFromRequest();
        PropertyNameMapper mapping = mappingsForClass(propertyMappings,
                beanDesc.getBeanClass());

        if (mapping == null) {
            return beanProperties;
        }

        List<BeanPropertyWriter> propsToWrite = new ArrayList<BeanPropertyWriter>();
        for (BeanPropertyWriter propWriter : beanProperties) {
            String propName = propWriter.getName();
            String outputName = mapping.nameMappings.get(propName);
            if (outputName != null) {
                BeanPropertyWriter modifiedWriter = new MyBeanPropertyWriter(
                        propWriter, outputName);
                propsToWrite.add(modifiedWriter);
            } else {
                propsToWrite.add(propWriter);
            }
        }
        return propsToWrite;
    }

    private List<PropertyNameMapper> getNameMappingsFromRequest() {
        RequestAttributes requestAttribs = RequestContextHolder
                .getRequestAttributes();
        List<PropertyNameMapper> nameMappings = (List<PropertyNameMapper>) requestAttribs
                .getAttribute("nameMappings",
                        RequestAttributes.SCOPE_REQUEST);
        return nameMappings;
    }

    private PropertyNameMapper mappingsForClass(
            List<PropertyNameMapper> nameMappings, Class<?> beanClass) {
        for (PropertyNameMapper mapping : nameMappings) {
            if (mapping.classToFilter.equals(beanClass)) {
                return mapping;
            }
        }
        return null;
    }
}

Now, you need a custom Module to be able to customize the output using the above created BeanSerializerModifier:

public class MyModule extends Module {

    @Override
    public String getModuleName() {
        return "Test Module";
    }

    @Override
    public void setupModule(SetupContext context) {
        context.addBeanSerializerModifier(new MySerializerModifier());
    }

    @Override
    public Version version() {
        // Modify if you need to.
        return Version.unknownVersion();
    }
}

Now register this module with your ObjectMapper. You can get the Jackson HTTP message converter from your spring application context, and get its object mapper.

// Figure out a way to get the ObjectMapper.
MappingJackson2HttpMessageConverter converter = ... // get the jackson-mapper;
converter.getObjectMapper().registerModule(new MyModule())

And that's it. This is the easiest way to customize serialization of your properties dynamically.

To use this, create a List of PropertyNameMappers and add it as an attribute (named "nameMappings" in this example) in the current request.

This is an example, not production-ready code. You might probably need to add null-checks and things like that. Also, a few minor adjustments might be needed based on the version of the libraries you are using.

If the solution doesn't work for you, let me know the problems you are facing.

Upvotes: 4

Related Questions