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

com.arakelian.jackson.model.Jackson Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.arakelian.jackson.model;

import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.apache.commons.io.input.CharSequenceReader;
import org.apache.commons.lang3.StringUtils;
import org.immutables.value.Value;

import com.arakelian.core.feature.Nullable;
import com.arakelian.jackson.databind.EnumUppercaseDeserializerModifier;
import com.arakelian.jackson.databind.TrimWhitespaceDeserializer;
import com.arakelian.jackson.databind.ZonedDateTimeDeserializer;
import com.arakelian.jackson.databind.ZonedDateTimeSerializer;
import com.arakelian.jackson.utils.JsonGeneratorCallback;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.MapType;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;

@Value.Immutable
public abstract class Jackson {
    /** Format dates as ISO string: "yyyy-MM-dd'T'HH:mm:ss.SSSZ" **/
    public static final SimpleModule DATE_MODULE = new SimpleModule() //
            .addSerializer(new ZonedDateTimeSerializer())
            .addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer());

    /** Allow enumerations to be upper or lowercase **/
    public static final SimpleModule ENUM_MODULE = new SimpleModule()
            .setDeserializerModifier(new EnumUppercaseDeserializerModifier());

    /** Trim leading and trailing whitespace, which includes tabs and newlines **/
    public static final SimpleModule TRIM_MODULE = new SimpleModule()
            .addDeserializer(String.class, TrimWhitespaceDeserializer.SINGLETON);

    private static final ObjectMapper DEFAULT_OBJECT_MAPPER = defaultBuilder().build() //
            .getObjectMapper();

    @SuppressWarnings(value = { "deprecation", "immutables:incompat" })
    public static ImmutableJackson.Builder defaultBuilder() {
        return ImmutableJackson.builder() //
                // general configuration
                .locale(Locale.getDefault()) //
                .pretty(true) //
                .serializationInclusion(JsonInclude.Include.NON_EMPTY) //

                // modules
                .findAndRegisterModules(true) //
                .registerTrimModule(true) //
                .registerDateModule(true) //
                .registerEnumModule(true) //

                // avoid unnecessary exception if module registered automatically and manually
                .putMapperFeatures(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS, true) //

                // collections are often immutable and not suitable for updates
                .putMapperFeatures(MapperFeature.USE_GETTERS_AS_SETTERS, false) //

                // forgiving parser
                .putParserFeatures(JsonParser.Feature.ALLOW_COMMENTS, true) //
                .putParserFeatures(JsonParser.Feature.ALLOW_YAML_COMMENTS, true) //
                .putParserFeatures(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) //
                .putParserFeatures(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) //
                .putParserFeatures(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true) //

                // serialization options
                .putSerializationFeatures(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false) //
                .putSerializationFeatures(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) //
                .putSerializationFeatures(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) //

                // strict rules regarding data integrity
                .putDeserializationFeatures(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true) //
                .putDeserializationFeatures(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, true) //
                .putDeserializationFeatures(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true) //
                .putDeserializationFeatures(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) //

                // we don't want big decimals output in scientific notation
                .putGeneratorFeatures(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true) //
        ;
    }

    public static ImmutableJackson.Builder from(final ObjectMapper mapper) {
        return ImmutableJackson.builder().defaultMapper(mapper);
    }

    public static ImmutableJackson.Builder fromDefault() {
        return from(DEFAULT_OBJECT_MAPPER);
    }

    public static Jackson of() {
        return fromDefault().build();
    }

    public static Jackson of(final ObjectMapper mapper) {
        return from(mapper).build();
    }

    public CharSequence buildJson(final Object... keyValues) {
        final int length = keyValues.length;
        Preconditions.checkArgument(
                length % 2 == 0,
                "Expected key-value pairs, but received array with odd number of entries");

        return toCharSequence(true, gen -> {
            gen.writeStartObject();
            for (int i = 0; i < length;) {
                final String key = Objects.toString(keyValues[i++], null);
                if (key == null) {
                    continue;
                }
                final Object value = keyValues[i++];
                if (value != null) {
                    gen.writeObjectField(key, value);
                }
            }
            gen.writeEndObject();
        });
    }

    public JsonNode buildJsonNode(final Object... keyValues) {
        final CharSequence json = buildJson(keyValues);
        try {
            return readValue(new CharSequenceReader(json), JsonNode.class);
        } catch (final IOException e) {
            // should not happen since we serialized JSON ourselves
            throw new UncheckedIOException(e);
        }
    }

    public  T convertValue(final Object value, final Class valueType) {
        return getObjectMapper().convertValue(value, valueType);
    }

    public Map convertValueToMap(final Object value) {
        return convertValueToMap(value, String.class, Object.class);
    }

    public  Map convertValueToMap(
            final Object value,
            final Class keyType,
            final Class valueType) {
        final MapType type = mapType(LinkedHashMap.class, keyType, valueType);
        return getObjectMapper().convertValue(value, type);
    }

    public JsonGenerator createGenerator(final Writer writer, final boolean pretty) throws IOException {
        final JsonGenerator generator = getObjectMapper().getFactory().createGenerator(writer);
        if (pretty) {
            return generator.useDefaultPrettyPrinter();
        } else {
            return generator;
        }
    }

    private ObjectWriter createObjectWriter(final Class view, final boolean pretty) {
        final ObjectMapper mapper = getObjectMapper();

        if (view != null) {
            if (pretty) {
                return mapper.writerWithView(view).withDefaultPrettyPrinter();
            } else {
                return mapper.writerWithView(view).withoutFeatures(SerializationFeature.INDENT_OUTPUT);
            }
        } else {
            if (pretty) {
                return mapper.writerWithDefaultPrettyPrinter();
            } else {
                return mapper.writer().withoutFeatures(SerializationFeature.INDENT_OUTPUT);
            }
        }
    }

    @Override
    public boolean equals(final Object obj) {
        // use reference equality
        return super.equals(obj);
    }

    public ImmutableJackson.Builder from() {
        return from(getObjectMapper());
    }

    @Nullable
    @Value.Auxiliary
    public abstract ObjectMapper getDefaultMapper();

    @Value.Auxiliary
    public abstract Map getDeserializationFeatures();

    @Value.Auxiliary
    public abstract Map getGeneratorFeatures();

    @Nullable
    public abstract Locale getLocale();

    public abstract Map getMapperFeatures();

    @Value.Default
    public Set getModules() {
        final ImmutableSet.Builder modules = ImmutableSet. builder();
        if (isRegisterTrimModule()) {
            modules.add(TRIM_MODULE);
        }
        if (isRegisterEnumModule()) {
            modules.add(ENUM_MODULE);
        }
        if (isRegisterDateModule()) {
            modules.add(DATE_MODULE);
        }
        return modules.build();
    }

    @SuppressWarnings("deprecation")
    @Value.Lazy
    public ObjectMapper getObjectMapper() {
        final ObjectMapper mapper;

        final ObjectMapper defaultMapper = getDefaultMapper();
        if (defaultMapper != null) {
            mapper = defaultMapper;
        } else {
            mapper = new ObjectMapper();
        }

        // set locale
        final Locale locale = getLocale();
        if (locale != null) {
            mapper.setLocale(locale);
        }

        final Map features = getGeneratorFeatures();
        for (final Feature feature : features.keySet()) {
            mapper.configure(feature, features.get(feature).booleanValue());
        }

        final Map serializationFeatures = getSerializationFeatures();
        for (final SerializationFeature feature : serializationFeatures.keySet()) {
            mapper.configure(feature, serializationFeatures.get(feature).booleanValue());
        }

        final Boolean pretty = isPretty();
        if (pretty != null) {
            mapper.configure(SerializationFeature.INDENT_OUTPUT, pretty.booleanValue());
        }

        final Map deserializationFeatures = getDeserializationFeatures();
        for (final DeserializationFeature feature : deserializationFeatures.keySet()) {
            mapper.configure(feature, deserializationFeatures.get(feature).booleanValue());
        }

        final Map parserFeatures = getParserFeatures();
        for (final JsonParser.Feature feature : parserFeatures.keySet()) {
            mapper.configure(feature, parserFeatures.get(feature).booleanValue());
        }

        final Map mapperFeatures = getMapperFeatures();
        for (final MapperFeature feature : mapperFeatures.keySet()) {
            mapper.configure(feature, mapperFeatures.get(feature).booleanValue());
        }

        // only register modules after mapper features have been configured; there is at least one
        // mapper feature that allows us to ignore duplicate module registration errors
        if (isFindAndRegisterModules()) {
            mapper.findAndRegisterModules();
        }

        for (final Module module : getModules()) {
            mapper.registerModule(module);
        }

        final Class view = getView();
        if (view != null) {
            mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, isDefaultViewInclusion());
            mapper.setConfig(mapper.getSerializationConfig().withView(view));
            mapper.setConfig(mapper.getDeserializationConfig().withView(view));
        }

        final Include serializationInclusion = getSerializationInclusion();
        if (serializationInclusion != null) {
            mapper.setSerializationInclusion(serializationInclusion);
        }

        return mapper;
    }

    @Value.Lazy
    public ObjectWriter getObjectWriter() {
        return createObjectWriter(getView(), false);
    }

    public ObjectWriter getObjectWriter(final Class view, final boolean pretty) {
        if (view == getView()) {
            if (pretty) {
                return getObjectWriterWithPrettyPrinter();
            } else {
                return getObjectWriter();
            }
        }
        return createObjectWriter(view, pretty);
    }

    @Value.Lazy
    public ObjectWriter getObjectWriterWithPrettyPrinter() {
        return createObjectWriter(getView(), true);
    }

    public abstract Map getParserFeatures();

    public abstract Map getSerializationFeatures();

    @Nullable
    public abstract JsonInclude.Include getSerializationInclusion();

    @Nullable
    public abstract Class getView();

    @Override
    public int hashCode() {
        // use default hash code
        return super.hashCode();
    }

    @Value.Default
    public boolean isDefaultViewInclusion() {
        return true;
    }

    @Value.Default
    public boolean isFindAndRegisterModules() {
        return false;
    }

    @Nullable
    public abstract Boolean isPretty();

    @Value.Default
    public boolean isRegisterDateModule() {
        return true;
    }

    @Value.Default
    public boolean isRegisterEnumModule() {
        return true;
    }

    @Value.Default
    public boolean isRegisterTrimModule() {
        return true;
    }

    public  MapType mapType(
            final Class mapClass,
            final Class keyType,
            final Class valueType) {
        Preconditions.checkArgument(keyType != null, "keyType must be non-null");
        Preconditions.checkArgument(valueType != null, "valueType must be non-null");
        final MapType type = getObjectMapper().getTypeFactory()
                .constructMapType(mapClass, keyType, valueType);
        return type;
    }

    public  T readValue(final Reader src, final Class valueType) throws IOException {
        return getObjectMapper().readValue(src, valueType);
    }

    public  T readValue(final String json, final Class type) throws IOException {
        return StringUtils.isEmpty(json) ? null : getObjectMapper().readValue(json, type);
    }

    @SuppressWarnings("TypeParameterUnusedInFormals")
    public  T readValue(final String json, final JavaType valueType) throws IOException {
        return StringUtils.isEmpty(json) ? null : getObjectMapper().readValue(json, valueType);
    }

    public  T readValue(final String json, final TypeReference valueType) throws IOException {
        return StringUtils.isEmpty(json) ? null : getObjectMapper().readValue(json, valueType);
    }

    public  Map readValueAsMap(
            final String json,
            final Class keyType,
            final Class valueType) throws IOException {
        if (StringUtils.isEmpty(json)) {
            return Collections. emptyMap();
        }

        final MapType type = mapType(LinkedHashMap.class, keyType, valueType);
        return getObjectMapper().readValue(json, type);
    }

    public CharSequence toCharSequence(final boolean pretty, final JsonGeneratorCallback callback)
            throws UncheckedIOException {
        final StringWriter out = new StringWriter(128);
        try (final JsonGenerator gen = createGenerator(out, pretty)) {
            callback.accept(gen);
        } catch (final IOException e) {
            throw new UncheckedIOException(e);
        }

        // let's give caller a chance to avoid a toString
        return out.getBuffer();
    }

    public CharSequence toCharSequence(final JsonGeneratorCallback callback) {
        return toCharSequence(true, callback);
    }

    public String toString(final Object value) throws UncheckedIOException {
        if (value == null) {
            return StringUtils.EMPTY;
        }
        try {
            return getObjectMapper().writeValueAsString(value);
        } catch (final IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public String toString(final Object value, final boolean pretty) throws UncheckedIOException {
        if (value == null) {
            return StringUtils.EMPTY;
        }
        try {
            final ObjectWriter w = pretty ? getObjectWriterWithPrettyPrinter() : getObjectWriter();
            return w.writeValueAsString(value);
        } catch (final IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy