JacksonMediaTypeCodec.java

/*
 * Copyright 2017-2021 original authors
 *
 * 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
 *
 * https://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.micronaut.jackson.codec;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.micronaut.context.BeanProvider;
import io.micronaut.core.type.Argument;
import io.micronaut.http.MediaType;
import io.micronaut.http.codec.CodecConfiguration;
import io.micronaut.http.codec.CodecException;
import io.micronaut.jackson.JacksonConfiguration;
import io.micronaut.jackson.databind.JacksonDatabindMapper;
import io.micronaut.json.JsonFeatures;
import io.micronaut.json.JsonMapper;
import io.micronaut.json.codec.MapperMediaTypeCodec;
import io.micronaut.runtime.ApplicationConfiguration;

import java.io.IOException;

/**
 * A {@link io.micronaut.http.codec.MediaTypeCodec} for JSON and Jackson.
 * <p>
 * Note: will be replaced by {@link MapperMediaTypeCodec} in the future, but that class is currently experimental.
 *
 * @author Graeme Rocher
 * @since 1.0.0
 * @deprecated Replaced with message body writers / readers API
 */
@Deprecated(forRemoval = true, since = "4.7")
public abstract class JacksonMediaTypeCodec extends MapperMediaTypeCodec {

    public static final String REGULAR_JSON_MEDIA_TYPE_CODEC_NAME = "json";

    public JacksonMediaTypeCodec(BeanProvider<ObjectMapper> objectMapperProvider,
                                 ApplicationConfiguration applicationConfiguration,
                                 CodecConfiguration codecConfiguration,
                                 MediaType mediaType) {
        super(
            new JacksonDatabindMapper(objectMapperProvider.get()),
            applicationConfiguration,
            codecConfiguration,
            mediaType
        );
    }

    public JacksonMediaTypeCodec(BeanProvider<JacksonDatabindMapper> objectMapperProvider,
                                 ApplicationConfiguration applicationConfiguration,
                                 MediaType mediaType,
                                 CodecConfiguration codecConfiguration) {
        super(
            objectMapperProvider.get(),
            applicationConfiguration,
            codecConfiguration,
            mediaType
        );
    }

    public JacksonMediaTypeCodec(ObjectMapper objectMapper,
                                 ApplicationConfiguration applicationConfiguration,
                                 CodecConfiguration codecConfiguration,
                                 MediaType mediaType) {
        super(
            new JacksonDatabindMapper(objectMapper),
            applicationConfiguration,
            codecConfiguration,
            mediaType
        );
    }

    /**
     * @return The object mapper
     */
    public ObjectMapper getObjectMapper() {
        return ((JacksonDatabindMapper) getJsonMapper()).getObjectMapper();
    }

    @Override
    public MapperMediaTypeCodec cloneWithFeatures(JsonFeatures features) {
        return cloneWithFeatures((JacksonFeatures) features);
    }

    /**
     * Create a new codec with the provided features.
     *
     * @param jacksonFeatures The jackson features
     * @return The new codec
     */
    public abstract JacksonMediaTypeCodec cloneWithFeatures(JacksonFeatures jacksonFeatures);

    @Override
    protected MapperMediaTypeCodec cloneWithMapper(JsonMapper mapper) {
        throw new UnsupportedOperationException();
    }

    /**
     * Decodes the given JSON node.
     *
     * @param type The type
     * @param node The Json Node
     * @param <T> The generic type
     * @return The decoded object
     * @throws CodecException When object cannot be decoded
     */
    public <T> T decode(Argument<T> type, JsonNode node) throws CodecException {
        try {
            ObjectMapper objectMapper = getObjectMapper();
            if (type.hasTypeVariables()) {
                JsonParser jsonParser = objectMapper.treeAsTokens(node);
                return objectMapper.readValue(jsonParser, constructJavaType(type));
            } else {
                return objectMapper.treeToValue(node, type.getType());
            }
        } catch (IOException e) {
            throw new CodecException("Error decoding JSON stream for type [" + type.getName() + "]: " + e.getMessage(), e);
        }
    }

    private <T> JavaType constructJavaType(Argument<T> type) {
        TypeFactory typeFactory = getObjectMapper().getTypeFactory();
        return JacksonConfiguration.constructType(type, typeFactory);
    }
}