/*
* 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: - RuntimeException – if no serializer supporting the given type can be found
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;
}
}