io.coala.json.JsonUtil.java Source code

Java tutorial

Introduction

Here is the source code for io.coala.json.JsonUtil.java

Source

/* $Id: c91de184ef39b6ed4344ca6210b70175eeea4c8f $
 * 
 * @license
 * 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 io.coala.json;

import java.beans.PropertyEditorSupport;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Collectors;

import org.apache.logging.log4j.Logger;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.eaio.UUIDModule;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import io.coala.exception.Thrower;
import io.coala.json.DynaBean.BeanProxy;
import io.coala.log.LogUtil;
import io.coala.util.TypeArguments;

/**
 * {@link JsonUtil}
 * 
 * @version $Id: c91de184ef39b6ed4344ca6210b70175eeea4c8f $
 * @author Rick van Krevelen
 */
public class JsonUtil {

    /** */
    private static final Logger LOG = LogUtil.getLogger(JsonUtil.class);

    /** */
    private static final ObjectMapper JOM = new ObjectMapper();

    /** singleton design pattern constructor */
    static//private JsonUtil()
    {
        // singleton design pattern
        initialize(JOM);
    }

    /**
     * @param instance
     */
    public synchronized static void initialize(final ObjectMapper om) {
        om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

        final Module[] modules = { new JodaModule(), new UUIDModule(), new JavaTimeModule() };
        om.registerModules(modules);
        LOG.trace("Using jackson v: {} with modules: {}", om.version(),
                Arrays.asList(modules).stream().map(m -> m.getModuleName()).collect(Collectors.toList()));
    }

    /** */
    public synchronized static ObjectMapper getJOM() {
        return JOM;
    }

    /**
     * @param object the object to serialize/marshal
     * @return the (minimal) JSON representation
     */
    public static String stringify(final Object object) {
        final ObjectMapper om = getJOM();
        try {
            checkRegistered(om, object.getClass());
            return om.writer().writeValueAsString(object);
        } catch (final JsonProcessingException e) {
            Thrower.rethrowUnchecked(e);
            return null;
        }
    }

    /**
     * @param object the object to serialize/marshal
     * @return the (pretty) JSON representation
     */
    public static String toJSON(final Object object) {
        return toJSON(getJOM(), object);
    }

    /**
     * @param object the object to serialize/marshal as (pretty) JSON
     * @return the (pretty) JSON representation
     */
    public static String toJSON(final ObjectMapper om, final Object object) {
        try {
            return om
                    // .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                    .writer().withDefaultPrettyPrinter().writeValueAsString(object);
        } catch (final JsonProcessingException e) {
            return Thrower.rethrowUnchecked(e);
        }
    }

    /**
     * @param profile
     * @return
     */
    public static JsonNode toTree(final Object object) {
        final ObjectMapper om = getJOM();
        try {
            // checkRegistered(om, object.getClass());
            return om.valueToTree(object);
            //         return om.readTree( stringify( object ) );
        } catch (final Exception e) {
            return Thrower.rethrowUnchecked(e);
        }
    }

    /**
     * @param json the {@link InputStream} of JSON formatted value
     * @return the parsed/deserialized/unmarshalled {@link JsonNode} tree
     * @see ObjectMapper#readTree(InputStream)
     */
    public static JsonNode toTree(final InputStream json) {
        try {
            return json == null ? null : getJOM().readTree(json);
        } catch (final Exception e) {
            return Thrower.rethrowUnchecked(e);
        }
    }

    /**
     * @param json the JSON formatted value
     * @return the parsed/deserialized/unmarshalled {@link JsonNode} tree
     * @see ObjectMapper#readTree(String)
     */
    public static JsonNode toTree(final String json) {
        if (json == null || json.isEmpty())
            return null;
        try {
            return getJOM().readTree(json);
        } catch (final Exception e) {
            return Thrower.rethrowUnchecked(e);
        }
    }

    /**
     * @param value the {@link Object} to serialize
     * @return the {@link String} representation in pretty JSON format
     * @deprecated use {@link #toJSON(Object)}
     */
    @Deprecated
    public static String toPrettyJSON(final Object object) {
        return toJSON(object);
    }

    /**
     * @param value the {@link Object} to serialize
     * @return the {@link String} representation in JSON format
     * @deprecated use {@link #stringify(Object)}
     */
    @Deprecated
    public static String toString(final Object value) {
        return stringify(value);
    }

    /**
     * @param json the {@link InputStream}
     * @param resultType the type of result {@link Object}
     * @return the parsed/deserialized/unmarshalled {@link Object}
     */
    public static <T> T valueOf(final InputStream json, final Class<T> resultType, final Properties... imports) {
        if (json == null)
            return null;
        try {
            final ObjectMapper om = getJOM();
            return (T) om.readValue(json, checkRegistered(om, resultType, imports));
        } catch (final Exception e) {
            return Thrower.rethrowUnchecked(e);
        }
    }

    /**
     * @param json the JSON formatted {@link String} value
     * @param resultType the type of result {@link Object}
     * @param imports the {@link Properties} instances for default values, etc.
     * @return the parsed/deserialized/unmarshalled {@link Object}
     */
    public static <T> T valueOf(final String json, final Class<T> resultType, final Properties... imports) {
        return valueOf(getJOM(), json, resultType, imports);
    }

    /**
     * @param om the {@link ObjectMapper} used to parse/deserialize/unmarshal
     * @param json the JSON formatted {@link String} value
     * @param resultType the type of result {@link Object}
     * @param imports the {@link Properties} instances for default values, etc.
     * @return the parsed/deserialized/unmarshalled {@link Object}
     */
    public static <T> T valueOf(final ObjectMapper om, final String json, final Class<T> resultType,
            final Properties... imports) {
        if (json == null || json.equalsIgnoreCase("null"))
            return null;
        try {
            return (T) om.readValue(
                    !json.startsWith("\"") && resultType == String.class ? "\"" + json + "\"" : json,
                    checkRegistered(om, resultType, imports));
        } catch (final Throwable e) {
            return Thrower.rethrowUnchecked(e);
        }
    }

    /**
     * @param tree the partially parsed JSON {@link TreeNode}
     * @param resultType the type of result {@link Object}
     * @param imports the {@link Properties} instances for default values, etc.
     * @return the parsed/deserialized/unmarshalled {@link Object}
     */
    public static <T> T valueOf(final TreeNode tree, final Class<T> resultType, final Properties... imports) {
        return valueOf(getJOM(), tree, resultType, imports);
    }

    /**
     * @param om the {@link ObjectMapper} used to parse/deserialize/unmarshal
     * @param tree the partially parsed JSON {@link TreeNode}
     * @param resultType the type of result {@link Object}
     * @param imports the {@link Properties} instances for default values, etc.
     * @return the parsed/deserialized/unmarshalled {@link Object}
     */
    public static <T> T valueOf(final ObjectMapper om, final TreeNode tree, final Class<T> resultType,
            final Properties... imports) {
        if (tree == null)
            return null;
        // TODO add work-around for Wrapper sub-types?
        if (resultType.isMemberClass() && !Modifier.isStatic(resultType.getModifiers()))
            return Thrower.throwNew(IllegalArgumentException.class, "Unable to deserialize non-static member: {}",
                    resultType);

        try {
            return (T) om.treeToValue(tree, checkRegistered(om, resultType, imports));
        } catch (final Exception e) {
            return Thrower.rethrowUnchecked(e);
        }
    }

    /**
     * @param json the JSON formatted {@link String} value
     * @param typeReference the result type {@link TypeReference}
     * @param imports the {@link Properties} instances for default values, etc.
     * @return the parsed/deserialized/unmarshalled {@link Object}
     */
    public static <T> T valueOf(final String json, final TypeReference<T> typeReference,
            final Properties... imports) {
        return valueOf(getJOM(), json, typeReference, imports);
    }

    /**
     * @param om the {@link ObjectMapper} used to parse/deserialize/unmarshal
     * @param json the JSON formatted {@link String} value
     * @param typeReference the result type {@link TypeReference}
     * @param imports the {@link Properties} instances for default values, etc.
     * @return the parsed/deserialized/unmarshalled {@link Object}
     */
    @SuppressWarnings("unchecked")
    public static <T> T valueOf(final ObjectMapper om, final String json, final TypeReference<T> typeReference,
            final Properties... imports) {
        if (json == null)
            return null;
        try {
            final Class<?> rawType = om.getTypeFactory().constructType(typeReference).getRawClass();
            checkRegistered(om, rawType, imports);
            return (T) om.readValue(json, typeReference);
        } catch (final Exception e) {
            Thrower.rethrowUnchecked(e);
            return null;
        }
    }

    public static class JsonPropertyEditor<E> extends PropertyEditorSupport {
        /** */
        private Class<E> type;

        @SuppressWarnings({ "unchecked" })
        private JsonPropertyEditor() {
            this.type = (Class<E>) TypeArguments.of(JsonPropertyEditor.class, getClass()).get(0);
        }

        @Override
        public void setAsText(final String json) throws IllegalArgumentException {
            try {
                setValue(JsonUtil.valueOf(json, this.type));
            } catch (final Throwable e) {
                throw new IllegalArgumentException(
                        "Problem editing property of type: " + this.type.getName() + " from JSON value: " + json,
                        e);
            }
        }
    }

    /**
     * cache of registered {@link Wrapper} or {@link DynaBean} types per
     * {@link ObjectMapper}'s {@link #hashCode()}
     */
    public static final Map<ObjectMapper, Set<Class<?>>> JSON_REGISTRATION_CACHE = new WeakHashMap<>();

    /**
     * @param om the {@link ObjectMapper} used to parse/deserialize/unmarshal
     * @param type the {@link Class} to register
     * @param imports the {@link Properties} instances for default values, etc.
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static <T> Class<T> checkRegistered(final ObjectMapper om, final Class<T> type,
            final Properties... imports) {
        synchronized (JSON_REGISTRATION_CACHE) {
            if (type.isPrimitive())
                return type;
            Set<Class<?>> cache = JSON_REGISTRATION_CACHE.computeIfAbsent(om, key -> new HashSet<>());
            if (type.getPackage() == Object.class.getPackage() || type.getPackage() == Collection.class.getPackage()
                    || type.isPrimitive()
                    // assume java.lang.* and java.util.* are already mapped
                    || TreeNode.class.isAssignableFrom(type) || cache.contains(type))
                return type;

            // use Class.forName(String) ?
            // see http://stackoverflow.com/a/9130560

            //         LOG.trace( "Register JSON conversion of type: {}", type    );
            if (type.isAnnotationPresent(BeanProxy.class)) {
                //            if( !type.isInterface() )
                //               return Thrower.throwNew( IllegalArgumentException.class,
                //                     "@{} must target an interface, but annotates: {}",
                //                     BeanProxy.class.getSimpleName(), type );

                DynaBean.registerType(om, type, imports);
                checkRegisteredMembers(om, type, imports);
                // LOG.trace("Registered Dynabean de/serializer for: " + type);
            } else if (Wrapper.class.isAssignableFrom(type)) {
                Wrapper.Util.registerType(om, (Class<? extends Wrapper>) type);
                checkRegisteredMembers(om, type, imports);
                // LOG.trace("Registered Wrapper de/serializer for: " + type);
            }
            // else
            // LOG.trace("Assume default de/serializer for: " + type);

            cache.add(type);

            return type;
        }
    }

    public static <T> Class<T> checkRegisteredMembers(final ObjectMapper om, final Class<T> type,
            final Properties... imports) {
        for (Method method : type.getDeclaredMethods())
            if (method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE
                    && method.getReturnType() != type) {
                //            LOG.trace(
                //                  "Checking {}#{}(..) return type: {} @JsonProperty={}",
                //                  type.getSimpleName(), method.getName(),
                //                  method.getReturnType().getSimpleName(),
                //                  method.getAnnotation( JsonProperty.class ) );
                checkRegistered(om, method.getReturnType(), imports);
            }
        return type;
    }
}