/*
 * Copyright 2002-2020 the original author or 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 org.springframework.http.codec.json;

import java.lang.reflect.Type;
import java.util.Map;

import kotlinx.serialization.KSerializer;
import kotlinx.serialization.SerializersKt;
import kotlinx.serialization.descriptors.PolymorphicKind;
import kotlinx.serialization.json.Json;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.core.ResolvableType;
import org.springframework.core.codec.AbstractDecoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.MimeType;

Decode a byte stream into JSON and convert to Object's with kotlinx.serialization.

This decoder can be used to bind @Serializable Kotlin classes, open polymorphic serialization is not supported. It supports application/json and application/*+json with various character sets, UTF-8 being the default.

Decoding streams is not supported yet, see kotlinx.serialization/issues/1073 related issue.

Author:Sebastien Deleuze
Since:5.3
/** * Decode a byte stream into JSON and convert to Object's with * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * * <p>This decoder can be used to bind {@code @Serializable} Kotlin classes, * <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a> * is not supported. * It supports {@code application/json} and {@code application/*+json} with * various character sets, {@code UTF-8} being the default. * * <p>Decoding streams is not supported yet, see * <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a> * related issue. * * @author Sebastien Deleuze * @since 5.3 */
public class KotlinSerializationJsonDecoder extends AbstractDecoder<Object> { private static final Map<Type, KSerializer<Object>> serializerCache = new ConcurrentReferenceHashMap<>(); private final Json json; // String decoding needed for now, see https://github.com/Kotlin/kotlinx.serialization/issues/204 for more details private final StringDecoder stringDecoder = StringDecoder.allMimeTypes(StringDecoder.DEFAULT_DELIMITERS, false); public KotlinSerializationJsonDecoder() { this(Json.Default); } public KotlinSerializationJsonDecoder(Json json) { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); this.json = json; }
Configure a limit on the number of bytes that can be buffered whenever the input stream needs to be aggregated. This can be a result of decoding to a single DataBuffer, ByteBuffer, byte[], Resource, String, etc. It can also occur when splitting the input stream, e.g. delimited text, in which case the limit applies to data buffered between delimiters.

By default this is set to 256K.

Params:
  • byteCount – the max number of bytes to buffer, or -1 for unlimited
/** * Configure a limit on the number of bytes that can be buffered whenever * the input stream needs to be aggregated. This can be a result of * decoding to a single {@code DataBuffer}, * {@link java.nio.ByteBuffer ByteBuffer}, {@code byte[]}, * {@link org.springframework.core.io.Resource Resource}, {@code String}, etc. * It can also occur when splitting the input stream, e.g. delimited text, * in which case the limit applies to data buffered between delimiters. * <p>By default this is set to 256K. * @param byteCount the max number of bytes to buffer, or -1 for unlimited */
public void setMaxInMemorySize(int byteCount) { this.stringDecoder.setMaxInMemorySize(byteCount); }
Return the configured byte count limit.
/** * Return the {@link #setMaxInMemorySize configured} byte count limit. */
public int getMaxInMemorySize() { return this.stringDecoder.getMaxInMemorySize(); } @Override public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { try { serializer(elementType.getType()); return (super.canDecode(elementType, mimeType) && !CharSequence.class.isAssignableFrom(elementType.toClass())); } catch (Exception ex) { return false; } } @Override public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { return Flux.error(new UnsupportedOperationException()); } @Override public Mono<Object> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { return this.stringDecoder .decodeToMono(inputStream, elementType, mimeType, hints) .map(jsonText -> this.json.decodeFromString(serializer(elementType.getType()), jsonText)); }
Tries to find a serializer that can marshall or unmarshall instances of the given type using kotlinx.serialization. If no serializer can be found, an exception is thrown.

Resolved serializers are cached and cached results are returned on successive calls. TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed

Params:
  • type – the type to find a serializer for
Throws:
Returns:a resolved serializer for the given type
/** * Tries to find a serializer that can marshall or unmarshall instances of the given type * using kotlinx.serialization. If no serializer can be found, an exception is thrown. * <p>Resolved serializers are cached and cached results are returned on successive calls. * TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed * @param type the type to find a serializer for * @return a resolved serializer for the given type * @throws RuntimeException if no serializer supporting the given type can be found */
private KSerializer<Object> serializer(Type type) { KSerializer<Object> serializer = serializerCache.get(type); if (serializer == null) { serializer = SerializersKt.serializer(type); if (serializer.getDescriptor().getKind().equals(PolymorphicKind.OPEN.INSTANCE)) { throw new UnsupportedOperationException("Open polymorphic serialization is not supported yet"); } serializerCache.put(type, serializer); } return serializer; } }