All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wl4g.infra.common.serialize.JacksonUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 ~ 2025 the original author or authors. James Wong 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.wl4g.infra.common.serialize;

import static com.wl4g.infra.common.collection.CollectionUtils2.safeList;
import static com.wl4g.infra.common.lang.Assert2.hasTextOf;
import static com.wl4g.infra.common.lang.Assert2.notEmptyOf;
import static com.wl4g.infra.common.lang.Assert2.notNullOf;
import static java.util.Collections.emptyMap;
import static java.util.Collections.synchronizedMap;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import javax.annotation.Nullable;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import com.fasterxml.jackson.core.FormatSchema;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.filter.TokenFilter;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.ContextAttributes;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.DataFormatReaders;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.wl4g.infra.common.collection.CollectionUtils2;
import com.wl4g.infra.common.lang.Assert2;
import com.wl4g.infra.common.reflect.ResolvableType;
import com.wl4g.infra.common.reflect.TypeUtils2;

import lombok.Getter;
import lombok.ToString;

/**
 * JACKSON utility tools.
 * 
 * @author James Wong 
 * @version v1.0 2019年05月22日
 * @since
 */
@SuppressWarnings("deprecation")
public abstract class JacksonUtils {

    //
    // --- Serialize Function. ---
    //

    /**
     * Object to JSON strings.
     * 
     * @param object
     * @return
     */
    public static String toJSONString(final @Nullable Object object) {
        return toJSONString(object, false);
    }

    /**
     * Object to JSON strings.
     * 
     * @param object
     * @param isPretty
     * @return
     */
    public static String toJSONString(final @Nullable Object object, final boolean isPretty) {
        return toJSONString(DEFAULT_OBJECT_MAPPER, object, isPretty, null, DEFAULT_EXCLUDER);
    }

    /**
     * Object to JSON strings.
     * 
     * @param mapper
     *            When using a custom modifier, you should use an independent
     *            objectMapper, because the same objectmapper instance will
     *            cache the serializer of the target bean, which may cause the
     *            modifier to fail, see source code:
     *            {@link com.fasterxml.jackson.databind.SerializerProvider#findTypedValueSerializer(java.lang.Class,boolean,com.fasterxml.jackson.databind.BeanProperty)}
     *            {@link com.fasterxml.jackson.databind.SerializerProvider#_knownSerializers}
     *            {@link com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap#typedValueSerializer(com.fasterxml.jackson.databind.JavaType)}
     * @param object
     * @param isPretty
     * @param excluder
     * @return
     */
    public static String toJSONString(
            final @NotNull ObjectMapper mapper,
            final @Nullable Object object,
            final @Nullable PropertyExcluder excluder) {
        return toJSONString(mapper, object, false, null, excluder);
    }

    /**
     * Object to JSON strings.
     * 
     * @param mapper
     *            When using a custom modifier, you should use an independent
     *            objectMapper, because the same objectmapper instance will
     *            cache the serializer of the target bean, which may cause the
     *            modifier to fail, see source code:
     *            {@link com.fasterxml.jackson.databind.SerializerProvider#findTypedValueSerializer(java.lang.Class,boolean,com.fasterxml.jackson.databind.BeanProperty)}
     *            {@link com.fasterxml.jackson.databind.SerializerProvider#_knownSerializers}
     *            {@link com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap#typedValueSerializer(com.fasterxml.jackson.databind.JavaType)}
     * @param object
     * @param transformer
     * @param excluder
     * @return
     */
    public static String toJSONString(
            final @NotNull ObjectMapper mapper,
            final @Nullable Object object,
            final @Nullable PropertyTransformer transformer,
            final @Nullable PropertyExcluder excluder) {
        return toJSONString(mapper, object, false, transformer, excluder);
    }

    /**
     * Object to JSON strings. 
* see: https://www.baeldung.com/jackson-ignore-properties-on-serialization * * @param mapper * When using a custom modifier, you should use an independent * objectMapper, because the same objectmapper instance will * cache the serializer of the target bean, which may cause the * modifier to fail, see source code: * {@link com.fasterxml.jackson.databind.SerializerProvider#findTypedValueSerializer(java.lang.Class,boolean,com.fasterxml.jackson.databind.BeanProperty)} * {@link com.fasterxml.jackson.databind.SerializerProvider#_knownSerializers} * {@link com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap#typedValueSerializer(com.fasterxml.jackson.databind.JavaType)} * @param object * @param isPretty * @param transformer * @param excluder * @return */ public static String toJSONString( final @NotNull ObjectMapper mapper, final @Nullable Object object, final boolean isPretty, final @Nullable PropertyTransformer transformer, final @Nullable PropertyExcluder excluder) { return _toJSONString(mapper, null, object, isPretty, transformer, excluder); } /** * Object to JSON strings. * * @param view * @param isPretty * @param transformer * @param excluder * @return */ public static String toJSONString( final @Nullable Class view, final @Nullable Object object, final boolean isPretty, final @Nullable PropertyTransformer transformer, final @Nullable PropertyExcluder excluder) { return _toJSONString(DEFAULT_OBJECT_MAPPER, view, object, isPretty, transformer, excluder); } /** * Object to JSON strings.
* see: https://www.baeldung.com/jackson-ignore-properties-on-serialization * * @param mapper * When using a custom modifier, you should use an independent * objectMapper, because the same objectmapper instance will * cache the serializer of the target bean, which may cause the * modifier to fail, see source code: * {@link com.fasterxml.jackson.databind.SerializerProvider#findTypedValueSerializer(java.lang.Class,boolean,com.fasterxml.jackson.databind.BeanProperty)} * {@link com.fasterxml.jackson.databind.SerializerProvider#_knownSerializers} * {@link com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap#typedValueSerializer(com.fasterxml.jackson.databind.JavaType)} * @param object * @param view * @param isPretty * @param transformer * @param excluder * @return */ public static String _toJSONString( final @NotNull ObjectMapper mapper, final @Nullable Class view, final @Nullable Object object, final boolean isPretty, final @Nullable PropertyTransformer transformer, final @Nullable PropertyExcluder excluder) { notNullOf(mapper, "mapper"); if (isNull(object)) { return null; } try { final SerializationConfig config = mapper.getSerializationConfig() .withFilters(new SimpleFilterProvider().addFilter(TransformPropertyFilter.FILTER_ID, new TransformPropertyFilter(transformer, excluder))) .withView(view); final PrettyPrinter pp = isPretty ? config.getDefaultPrettyPrinter() : null; return new CustomObjectWriter(mapper, config, null, pp).writeValueAsString(object); } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } // // --- Deserialize with Collection Function. --- // /** * Parse array parameterized map string from JSON strings. * * @param json * @param valueTypeRef * @return */ public static List parseArrayString(final @Nullable String jsonString) { return parseArrayString(null, jsonString); } /** * Parse array parameterized map string from JSON strings. * * @param view * @param json * @param valueTypeRef * @return */ public static List parseArrayString(final @Nullable Class view, final @Nullable String jsonString) { return parseJSON(DEFAULT_OBJECT_MAPPER, view, jsonString, LIST_STRING_TYPE_REF, null, null); } /** * Parse array parameterized map string from JSON strings. * * @param json * @param valueTypeRef * @return */ public static List> parseArrayMapString(@Nullable String jsonString) { return parseArrayMapString(null, jsonString); } /** * Parse array parameterized map string from JSON strings. * * @param view * @param json * @param valueTypeRef * @return */ public static List> parseArrayMapString(final @Nullable Class view, @Nullable String jsonString) { return parseJSON(DEFAULT_OBJECT_MAPPER, view, jsonString, LIST_MAP_STRING_TYPE_REF, null, null); } /** * Parse array parameterized map object from JSON strings. * * @param view * @param json * @param valueTypeRef * @return */ public static List> parseArrayMapObject(final @Nullable Class view, @Nullable String jsonString) { return parseJSON(DEFAULT_OBJECT_MAPPER, view, jsonString, LIST_MAP_OBJECT_TYPE_REF, null, null); } /** * Parse map parameterized object from JSON strings. * * @param json * @param valueTypeRef * @return */ public static Map parseMapObject(@Nullable String jsonString) { return parseMapObject(null, jsonString); } /** * Parse map parameterized object from JSON strings. * * @param view * @param json * @param valueTypeRef * @return */ public static Map parseMapObject(final @Nullable Class view, @Nullable String jsonString) { return parseJSON(DEFAULT_OBJECT_MAPPER, view, jsonString, MAP_OBJECT_TYPE_REF, null, null); } // // --- Deserialize with JavaBean Function. --- // /** * Parse object from JSON strings. * * @param json * @param valueType * @return */ public static T parseJSON(final @Nullable String jsonString, final @NotNull Class valueType) { return parseJSON(DEFAULT_OBJECT_MAPPER, jsonString, valueType, null, null); } /** * Parse object string from JSON strings. * * @param view * @param jsonFile * @param valueType * @return */ public static T parseJSON( final @Nullable Class view, final @Nullable File jsonFile, final @NotNull Class valueType) { if (isNull(jsonFile)) { return null; } final ObjectMapper mapper = DEFAULT_OBJECT_MAPPER; return _parseJSON(mapper, null, () -> { try { return isNull(jsonFile) ? null : mapper.getFactory().createParser(jsonFile); } catch (IOException e) { throw new IllegalStateException(e); } }, () -> mapper.getTypeFactory().constructType(valueType), null, null); } /** * Parse object string from JSON strings. * * @param view * @param jsonInputStream * @param valueType * @return */ public static T parseJSON( final @Nullable Class view, final @Nullable InputStream jsonInputStream, final @NotNull Class valueType) { if (isNull(jsonInputStream)) { return null; } final ObjectMapper mapper = DEFAULT_OBJECT_MAPPER; return _parseJSON(mapper, null, () -> { try { return isNull(jsonInputStream) ? null : mapper.getFactory().createParser(jsonInputStream); } catch (IOException e) { throw new IllegalStateException(e); } }, () -> mapper.getTypeFactory().constructType(valueType), null, null); } /** * Parse object string from JSON strings. * * @param view * @param json * @param valueType * @return */ public static T parseJSON( final @Nullable Class view, final @Nullable String jsonString, final @NotNull Class valueType) { if (isBlank(jsonString)) { return null; } final ObjectMapper mapper = DEFAULT_OBJECT_MAPPER; return _parseJSON(mapper, null, () -> { try { return isNull(jsonString) ? null : mapper.getFactory().createParser(jsonString); } catch (IOException e) { throw new IllegalStateException(e); } }, () -> mapper.getTypeFactory().constructType(valueType), null, null); } /** * Parse object string from JSON strings. * * @param mapper * @param view * @param json * @param valueType * @param transformer * @param excluder * @return */ public static T parseJSON( final @NotNull ObjectMapper mapper, final @Nullable String jsonString, final @NotNull Class valueType, final @Nullable PropertyTransformer transformer, final @Nullable PropertyExcluder excluder) { if (isBlank(jsonString)) { return null; } return _parseJSON(mapper, null, () -> { try { return mapper.getFactory().createParser(jsonString); } catch (IOException e) { throw new IllegalStateException(e); } }, () -> mapper.getTypeFactory().constructType(valueType), transformer, excluder); } /** * Parse object string from JSON strings. * * @param json * @param valueTypeRef * @return */ public static T parseJSON(final @Nullable String jsonString, final @NotNull TypeReference valueTypeRef) { return parseJSON(DEFAULT_OBJECT_MAPPER, null, jsonString, valueTypeRef, null, null); } /** * Parse object string from JSON strings. * * @param view * @param json * @param valueTypeRef * @return */ public static T parseJSON( final @Nullable Class view, final @Nullable String jsonString, final @NotNull TypeReference valueTypeRef) { return parseJSON(DEFAULT_OBJECT_MAPPER, view, jsonString, valueTypeRef, null, null); } /** * Parse object string from JSON strings. * * @param mapper * @param json * @param valueTypeRef * @param transformer * @param excluder * @return */ public static T parseJSON( final @NotNull ObjectMapper mapper, final @Nullable String jsonString, final @NotNull TypeReference valueTypeRef, final @Nullable PropertyTransformer transformer, final @Nullable PropertyExcluder excluder) { return parseJSON(mapper, null, jsonString, valueTypeRef, transformer, excluder); } /** * Parse object string from JSON strings. * * @param mapper * @param view * @param json * @param valueTypeRef * @param transformer * @param excluder * @return */ public static T parseJSON( final @NotNull ObjectMapper mapper, final @Nullable Class view, final @Nullable String jsonString, final @NotNull TypeReference valueTypeRef, final @Nullable PropertyTransformer transformer, final @Nullable PropertyExcluder excluder) { return _parseJSON(mapper, view, () -> { try { return isNull(jsonString) ? null : mapper.getFactory().createParser(jsonString); } catch (IOException e) { throw new IllegalStateException(e); } }, () -> mapper.getTypeFactory().constructType(valueTypeRef), transformer, excluder); } /** * Parse object string from JSON strings. * * @param mapper * @param view * @param jsonParserSupplier * @param valueJavaTypeSupplier * @param transformer * @param excluder * @return */ public static T _parseJSON( final @NotNull ObjectMapper mapper, final @Nullable Class view, final @NotNull Supplier jsonParserSupplier, final @NotNull Supplier valueJavaTypeSupplier, final @Nullable PropertyTransformer transformer, final @Nullable PropertyExcluder excluder) { notNullOf(mapper, "mapper"); notNullOf(jsonParserSupplier, "jsonParserSupplier"); notNullOf(valueJavaTypeSupplier, "valueJavaTypeSupplier"); final JsonParser jsonParser = jsonParserSupplier.get(); if (isNull(jsonParser)) { return null; } try { final DeserializationConfig config = mapper.getDeserializationConfig() .with(new TransformContextAttributes(transformer, excluder)); return new CustomObjectReader(mapper, config).withView(view).readValue(jsonParser, valueJavaTypeSupplier.get()); } catch (Exception e) { throw new IllegalArgumentException(e); } } // // --- Deserialize with JsonNode Function. --- // /** * Parse {@link TreeNode} to object. * * @param object * @param atPathExpr * @return */ public static T parseFromNode(@Nullable TreeNode node, @NotBlank String atPathExpr, @NotNull Class valueType) { hasTextOf(atPathExpr, "atPathExpr"); if (isNull(node)) { return null; } try { final TreeNode objNode = node.at(atPathExpr); if (objNode.size() > 0) { return DEFAULT_OBJECT_MAPPER.treeToValue(objNode, valueType); } return null; } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } /** * Parse object to {@link JsonNode}. * * @param object * @param atPathExpr * @return */ public static JsonNode parseToNode(@Nullable String jsonString) { return parseToNode(jsonString, null); } /** * Parse object to {@link JsonNode}. * * @param object * @param atPathExpr * @return */ public static JsonNode parseToNode(@Nullable String jsonString, @Nullable String atPathExpr) { return parseToNode(DEFAULT_OBJECT_MAPPER, null, jsonString, atPathExpr); } /** * Parse object to {@link JsonNode}. * * @param view * @param object * @param atPathExpr * @return */ public static JsonNode parseToNode( final @Nullable Class view, final @Nullable String jsonString, final @Nullable String atPathExpr) { return parseToNode(DEFAULT_OBJECT_MAPPER, view, jsonString, atPathExpr); } /** * Parse object to {@link JsonNode}. * * @param mapper * @param view * @param jsonString * @param atPathExpr * @return */ public static JsonNode parseToNode( final @NotNull ObjectMapper mapper, final @Nullable Class view, final @Nullable String jsonString, final @Nullable String atPathExpr) { notNullOf(mapper, "mapper"); if (isBlank(jsonString)) { return null; } try { return mapper.readerWithView(view).readTree(jsonString).requiredAt(atPathExpr); } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } } // // --- Conversion Function. --- // /** * Convert value to target type.
* * @see com.fasterxml.jackson.databind.ObjectMapper#convertValue(Object, * Class) * @param * @param bean * @param toType * @return */ public static T convertBean(@Nullable Object bean, @NotNull Class toType) { notNullOf(toType, "toType"); if (isNull(bean)) { return null; } return DEFAULT_OBJECT_MAPPER.convertValue(bean, toType); } /** * Convert value to reference type.
* * @see com.fasterxml.jackson.databind.ObjectMapper#convertValue(Object, * TypeReference) * @param * @param bean * @param typeRef * @return */ public static T convertBean(@Nullable Object bean, @NotNull TypeReference valueTypeRef) { notNullOf(valueTypeRef, "valueTypeRef"); if (isNull(bean)) { return null; } return DEFAULT_OBJECT_MAPPER.convertValue(bean, valueTypeRef); } /** * The convert value to Java type.
* * @see com.fasterxml.jackson.databind.ObjectMapper#convertValue(Object, * JavaType) * @param * @param bean * @param toJavaType * @return */ public static T convertBean(@Nullable Object bean, @NotNull JavaType toJavaType) { notNullOf(toJavaType, "toJavaType"); if (isNull(bean)) { return null; } return DEFAULT_OBJECT_MAPPER.convertValue(bean, toJavaType); } /** * The deep cloning object with JSON serial-deserial. * * @param obj * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static T deepClone(@Nullable T obj) { if (isNull(obj)) { return null; } final ResolvableType resolver = ResolvableType.forClass(obj.getClass()); if (Collection.class.isAssignableFrom(obj.getClass())) { ObjectReader reader = null; ResolvableType[] generics = resolver.getGenerics(); if (!isNull(generics) && generics.length == 1) { Class valueType = generics[0].getRawClass(); if (!isNull(valueType)) { reader = DEFAULT_OBJECT_MAPPER.readerForListOf(valueType); } } if (isNull(reader)) { // Fallback Collection collect = (Collection) obj; if (collect.isEmpty()) { return obj; } Iterator it = collect.iterator(); if (it.hasNext()) { reader = DEFAULT_OBJECT_MAPPER.readerForListOf(it.next().getClass()); } } try { return reader.readValue(toJSONString(obj)); } catch (JsonProcessingException e) { throw new IllegalStateException(e); } } else if (Map.class.isAssignableFrom(obj.getClass())) { Map map = (Map) obj; Map cloneMap = new LinkedHashMap<>(map.size()); map.forEach((key, val) -> cloneMap.put(deepClone(key), deepClone(val))); return (T) cloneMap; } else if (TypeUtils2.isSimpleType(obj.getClass())) { // Simple Class return obj; } // Custom bean(obj field after recursion) return (T) parseJSON(toJSONString(obj), obj.getClass()); } /** * The override merge object to target. * * @param * @param obj * @param overrideObj * @return */ public static T mergeWithOverride(@Nullable T obj, @Nullable T overrideObj) { if (isNull(obj) && nonNull(overrideObj)) { return overrideObj; } if (isNull(overrideObj) && nonNull(obj)) { return obj; } if (isNull(obj) && isNull(overrideObj)) { return null; } try { final ObjectReader reader = DEFAULT_OBJECT_MAPPER.readerForUpdating(obj); return reader.readValue(toJSONString(overrideObj)); } catch (JsonProcessingException e) { throw new IllegalStateException(e); } } // // --- Helper Function. --- // @NotNull public static final ObjectMapper newDefaultObjectMapper() { return newDefaultObjectMapper(true); } /** * Create default {@link ObjectMapper} * * @param enableExtension * @return */ @NotNull public static final ObjectMapper newDefaultObjectMapper(final boolean enableExtension) { final ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.registerModule(new JavaTimeModule()); mapper.registerModule(new JSR310Module()); mapper.registerModule(new SimpleModule() { private static final long serialVersionUID = 1L; @Override public void setupModule(SetupContext context) { super.setupModule(context); if (enableExtension) { context.addBeanSerializerModifier(new TransformPropertiesSerializerModifier()); context.addBeanDeserializerModifier(new TransformPropertiesDeserializerModifier()); } } }); DefaultPrettyPrinter customPrettyPrinter = new DefaultPrettyPrinter(); customPrettyPrinter.indentArraysWith(DefaultIndenter.instance); // customPrettyPrinter.indentObjectsWith(NopIndenter.instance); mapper.setDefaultPrettyPrinter(customPrettyPrinter); return mapper; } public static class CustomObjectWriter extends ObjectWriter { private static final long serialVersionUID = 1L; protected CustomObjectWriter(ObjectMapper mapper, SerializationConfig config, JavaType rootType, PrettyPrinter pp) { super(mapper, config, rootType, pp); } protected CustomObjectWriter(ObjectMapper mapper, SerializationConfig config) { super(mapper, config); } protected CustomObjectWriter(ObjectMapper mapper, SerializationConfig config, FormatSchema s) { super(mapper, config, s); } protected CustomObjectWriter(ObjectWriter base, SerializationConfig config, GeneratorSettings genSettings, Prefetch prefetch) { super(base, config, genSettings, prefetch); } protected CustomObjectWriter(ObjectWriter base, SerializationConfig config) { super(base, config); } protected CustomObjectWriter(ObjectWriter base, JsonFactory f) { super(base, f); } } public static class CustomObjectReader extends ObjectReader { private static final long serialVersionUID = 1L; protected CustomObjectReader(ObjectMapper mapper, DeserializationConfig config) { super(mapper, config); } protected CustomObjectReader(ObjectMapper mapper, DeserializationConfig config, JavaType valueType, Object valueToUpdate, FormatSchema schema, InjectableValues injectableValues) { super(mapper, config, valueType, valueToUpdate, schema, injectableValues); } protected CustomObjectReader(ObjectReader base, DeserializationConfig config, JavaType valueType, JsonDeserializer rootDeser, Object valueToUpdate, FormatSchema schema, InjectableValues injectableValues, DataFormatReaders dataFormatReaders) { super(base, config, valueType, rootDeser, valueToUpdate, schema, injectableValues, dataFormatReaders); } protected CustomObjectReader(ObjectReader base, DeserializationConfig config) { super(base, config); } protected CustomObjectReader(ObjectReader base, JsonFactory f) { super(base, f); } protected CustomObjectReader(ObjectReader base, TokenFilter filter) { super(base, filter); } } // --- Custom Serializer. --- @Getter public static class TransformPropertyFilter extends SimpleBeanPropertyFilter { static final String FILTER_ID = TransformPropertyFilter.class.getSimpleName(); final @NotNull PropertyTransformer transformer; final @NotNull PropertyExcluder excluder; public TransformPropertyFilter(@Nullable PropertyTransformer transformer, @Nullable PropertyExcluder excluder) { this.transformer = isNull(transformer) ? DEFAULT_TRANSFORMER : transformer; this.excluder = isNull(excluder) ? DEFAULT_EXCLUDER : excluder; // Check the doesn't deserialze transformer. Assert2.notInstanceOf(DefaultDeserialzePropertyTransformer.class, transformer, "Serializing Json should not use deserializing transformer."); } @Override protected boolean include(BeanPropertyWriter writer) { // throw new UnsupportedOperationException(); return true; } @Override protected boolean include(PropertyWriter writer) { // throw new UnsupportedOperationException(); return true; } public String transform(@NotNull BeanDescription beanClass, @NotBlank String property) { notNullOf(beanClass, "superBeanClass"); hasTextOf(property, "property"); return transformer.transform(beanClass, property); } public boolean exclude(@NotNull BeanDescription beanClass, @NotBlank String property) { notNullOf(beanClass, "superBeanClass"); hasTextOf(property, "property"); return excluder.filter(beanClass, property); } } public static class TransformPropertiesSerializerModifier extends BeanSerializerModifier { // In this method you can add, remove or replace any of passed // properties. @Override public List changeProperties( SerializationConfig config, BeanDescription beanDesc, List beanProperties) { final FilterProvider provider = config.getFilterProvider(); if (nonNull(provider)) { // Filtering for ignore properties. final TransformPropertyFilter filter = notNullOf( provider.findPropertyFilter(TransformPropertyFilter.FILTER_ID, null), "filter"); // Transform properties. return safeList(beanProperties).stream() // The field type => bp.getType().getRawClass() .filter(bp -> !filter.exclude(beanDesc, bp.getName())) .map(bp -> bp.rename(new NameTransformer() { @Override public String transform(String name) { return filter.transform(beanDesc, name); } @Override public String reverse(String transformed) { return transformed; } })) .collect(toList()); } return beanProperties; } } // --- Custom deserializer. --- @Getter public static class TransformContextAttributes extends ContextAttributes.Impl { private static final long serialVersionUID = 1L; final @NotNull PropertyTransformer transformer; final @NotNull PropertyExcluder excluder; public TransformContextAttributes(@Nullable PropertyTransformer transformer, @Nullable PropertyExcluder excluder) { super(emptyMap()); // Nothing shared this.transformer = isNull(transformer) ? DEFAULT_TRANSFORMER : transformer; this.excluder = isNull(excluder) ? DEFAULT_EXCLUDER : excluder; } public String transform(@NotNull BeanDescription beanClass, @NotBlank String property) { notNullOf(beanClass, "superBeanClass"); hasTextOf(property, "property"); return transformer.transform(beanClass, property); } public boolean exclude(@NotNull BeanDescription beanClass, @NotBlank String property) { notNullOf(beanClass, "superBeanClass"); hasTextOf(property, "property"); return excluder.filter(beanClass, property); } } public static class TransformBeanDeserializer extends BeanDeserializer { private static final long serialVersionUID = 1L; protected TransformBeanDeserializer(BeanDeserializerBase src, NameTransformer unwrapper) { super(src, unwrapper); } } public static class TransformPropertiesDeserializerModifier extends BeanDeserializerModifier { @Override public JsonDeserializer modifyDeserializer( DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer deserializer) { if (deserializer instanceof BeanDeserializerBase) { final ContextAttributes attr = config.getAttributes(); notNullOf(attr, "attributes"); if (attr instanceof TransformContextAttributes) { final TransformContextAttributes attributes = (TransformContextAttributes) attr; // Transform properties. return new TransformBeanDeserializer((BeanDeserializerBase) deserializer, new NameTransformer() { @Override public String transform(String name) { return attributes.transform(beanDesc, name); } @Override public String reverse(String transformed) { return transformed; } }); } } return deserializer; } @Override public List updateProperties( DeserializationConfig config, BeanDescription beanDesc, List propDefs) { // Remove for ignore properties. if (nonNull(config.getAttributes()) && config.getAttributes() instanceof TransformContextAttributes) { final TransformContextAttributes attributes = (TransformContextAttributes) config.getAttributes(); final Iterator it = safeList(propDefs).iterator(); while (it.hasNext()) { final BeanPropertyDefinition definition = it.next(); // The field type => definition.getField().getRawType() if (attributes.exclude(beanDesc, definition.getName())) { it.remove(); } } } return propDefs; } } @Getter @ToString public static class DefaultDeserialzePropertyTransformer implements PropertyTransformer { private final @Nullable Map, Map> transformProperties = synchronizedMap(new HashMap<>()); public DefaultDeserialzePropertyTransformer(@NotNull Class superBeanClass, @NotEmpty Map transformProps) { notNullOf(superBeanClass, "superBeanClass"); notEmptyOf(transformProps, "transformProps"); this.transformProperties.put(superBeanClass, transformProps); } public DefaultDeserialzePropertyTransformer(@Nullable Map, Map> transformProperties) { if (!CollectionUtils2.isEmpty(transformProperties)) { this.transformProperties.putAll(transformProperties); } } @Override public String transform(@NotNull BeanDescription beanDesc, @NotBlank String property) { // Empty transform configuration does nothing if (CollectionUtils2.isEmpty(transformProperties)) { return property; } // // Gets directly according to beanClass (superclass matches is not // supported). // final Map transformProps = // transformProperties.get(beanDesc.getBeanClass()); // // Support to get by superclass matches. final Map transformProps = transformProperties.entrySet() .stream() .filter(e -> e.getKey().isAssignableFrom(beanDesc.getBeanClass())) .flatMap(e -> e.getValue().entrySet().stream()) .collect(toMap(e -> e.getKey(), e -> e.getValue())); // Do nothing when the beanClass does not match the conversion // configuration. if (isNull(transformProps)) { return property; } // Notice: It needs to be reversed here, that is, from // key->value to value->key, because the java bean property // name is modified here, that is, the modification of the // java bean property name to be consistent with the key in // the json string will take effect. return transformProps.entrySet() .stream() // to reverse mapped. .collect(toMap(e -> e.getValue(), e -> e.getKey())) .getOrDefault(property, property); } } public static interface PropertyTransformer { String transform(final @NotNull BeanDescription beanDesc, @NotBlank String property); } public static interface PropertyExcluder { boolean filter(final @NotNull BeanDescription beanDesc, @NotBlank String property); } /** * Default {@link ObjectMapper} instance. */ public static final ObjectMapper DEFAULT_OBJECT_MAPPER = newDefaultObjectMapper(); public static final PropertyTransformer DEFAULT_TRANSFORMER = (beanDesc, property) -> property; public static final PropertyExcluder DEFAULT_EXCLUDER = (beanDesc, property) -> false; public static final TypeReference> LIST_STRING_TYPE_REF = new TypeReference>() { }; public static final TypeReference>> LIST_MAP_STRING_TYPE_REF = new TypeReference>>() { }; public static final TypeReference>> LIST_MAP_OBJECT_TYPE_REF = new TypeReference>>() { }; public static final TypeReference> MAP_OBJECT_TYPE_REF = new TypeReference>() { }; }