Merge JSON Objects: Jackson + BeanUtils.copyProperties


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.

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

Labels

adsense (5) Algorithm (69) Algorithm Series (35) Android (7) ANT (6) bat (8) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) Java (186) JavaScript (27) JSON (7) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) regex (5) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) xml (5)