The Problem
We store some configuration object as JSON string and want client able to update separate fields directly - without reading it first.
The Solution
In our java configuration class, we use wrapper class for simple types, then Spring BeanUtils.copyProperties(Object source, Object target, String... ignoreProperties) to copy from new config to old config and ignore the null values in new config.
If the configuration has f1=1, f2=1, f3=1, the client only wants to change f3 to 3, then the client can change just send {f3: 3} without having to read the data first.
This is why we use wrapper instead of primitive fields, as the value would be null in new config if the client doesn't specify, we can add all null fields to the ignoreProperties.
Jackson Essentials - the JSON Libaray
Using Jackson JSON View to Protect Mass Assignment Vulnerabilities
Merge JSON Objects: Jackson + BeanUtils.copyProperties
Jackson Generic Type + Java Type Erasure
Jackson Date Serialize + Deserialize
We store some configuration object as JSON string and want client able to update separate fields directly - without reading it first.
The Solution
In our java configuration class, we use wrapper class for simple types, then Spring BeanUtils.copyProperties(Object source, Object target, String... ignoreProperties) to copy from new config to old config and ignore the null values in new config.
If the configuration has f1=1, f2=1, f3=1, the client only wants to change f3 to 3, then the client can change just send {f3: 3} without having to read the data first.
This is why we use wrapper instead of primitive fields, as the value would be null in new config if the client doesn't specify, we can add all null fields to the ignoreProperties.
public class SimpleConfig implements Serializable { private static final long serialVersionUID = 1L; private Boolean featureAEnabled; private Integer minVersion; // other fields ignored } public void mergeSimpleConfig(final SimpleConfig newConfig) { final String configName = COLUMN_SIMPLE_CONFIG; final SimpleConfig oldConfig = getSimpleConfig(); SimpleConfig mergedConfig = (SimpleConfig) SerializationUtils.clone(oldConfig); mergeIgnoreNullProperties(newConfig, mergedConfig); final ConfigElement newConfigElement = new ConfigElement(); newConfigElement.setConfigName(configName); newConfigElement.setValue(objectMapper.writeValueAsString(mergedConfig)); newConfigElement.setLastUpdateTime(new Date()); configDao.saveConfig(newConfigElement); } public static <T> void mergeIgnoreNullProperties(final T source, final T target) { BeanUtils.copyProperties(source, target, CobraUtil.getNullPropertyNames(source)); } public static String[] getNullPropertyNames(final Object source) { final BeanWrapper src = new BeanWrapperImpl(source); final java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors(); final Set<String> emptyNames = new HashSet<String>(); for (final java.beans.PropertyDescriptor pd : pds) { final Object srcValue = src.getPropertyValue(pd.getName()); if (srcValue == null) { emptyNames.add(pd.getName()); } } final String[] result = new String[emptyNames.size()]; return emptyNames.toArray(result); } @Component @Provider public class JerseyObjectMapperProvider implements ContextResolver<ObjectMapper> { private static ObjectMapper objectMapper = createFailSafeObjectmapper(); @Override public ObjectMapper getContext(final Class<?> type) { return objectMapper; } public static ObjectMapper createFailSafeObjectmapper() { return new ObjectMapper() .setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")) .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false) .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false) .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false) .configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false) .configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, false) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS, false) .setSerializationInclusion(Include.NON_NULL); } }Read More
Jackson Essentials - the JSON Libaray
Using Jackson JSON View to Protect Mass Assignment Vulnerabilities
Merge JSON Objects: Jackson + BeanUtils.copyProperties
Jackson Generic Type + Java Type Erasure
Jackson Date Serialize + Deserialize