/*
 * 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.List;
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.AbstractEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.MimeType;

Encode from an Object stream to a byte stream of JSON objects using kotlinx.serialization.

This encoder 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.

Author:Sebastien Deleuze
Since:5.3
/** * Encode from an {@code Object} stream to a byte stream of JSON objects using * <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>. * * <p>This encoder 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. * * @author Sebastien Deleuze * @since 5.3 */
public class KotlinSerializationJsonEncoder extends AbstractEncoder<Object> { private static final Map<Type, KSerializer<Object>> serializerCache = new ConcurrentReferenceHashMap<>(); private final Json json; // CharSequence encoding needed for now, see https://github.com/Kotlin/kotlinx.serialization/issues/204 for more details private final CharSequenceEncoder charSequenceEncoder = CharSequenceEncoder.allMimeTypes(); public KotlinSerializationJsonEncoder() { this(Json.Default); } public KotlinSerializationJsonEncoder(Json json) { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); this.json = json; } @Override public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType) { try { serializer(elementType.getType()); return (super.canEncode(elementType, mimeType) && !String.class.isAssignableFrom(elementType.toClass()) && !ServerSentEvent.class.isAssignableFrom(elementType.toClass())); } catch (Exception ex) { return false; } } @Override public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory bufferFactory, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { if (inputStream instanceof Mono) { return Mono.from(inputStream) .map(value -> encodeValue(value, bufferFactory, elementType, mimeType, hints)) .flux(); } else { ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType); return Flux.from(inputStream) .collectList() .map(list -> encodeValue(list, bufferFactory, listType, mimeType, hints)) .flux(); } } @Override public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory, ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { String json = this.json.encodeToString(serializer(valueType.getType()), value); return this.charSequenceEncoder.encodeValue(json, bufferFactory, valueType, mimeType, null); }
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; } }