Skip to content

Serializer for a collection with specific type breaks default generic collection serializer #1187

@simon04

Description

@simon04

Expected Behavior

Follow-up for #690, but for Serializer instead of Deserializer

  1. Define Serializer<List<Something>>
  2. Serialize List<Something>
  3. Serialize List<String>

Actual Behaviour

  1. Define Serializer<List<Something>>
  2. Serialize List<Something> okay
  3. Serialize List<String> yields ClassCastException

Steps To Reproduce

package x;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.type.Argument;
import io.micronaut.serde.Decoder;
import io.micronaut.serde.Deserializer;
import io.micronaut.serde.Encoder;
import io.micronaut.serde.ObjectMapper;
import io.micronaut.serde.Serializer;
import io.micronaut.serde.annotation.Serdeable;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

@MicronautTest
public class SerdeTest {

	@Inject
	ObjectMapper objectMapper;

	record Something(String s) {
	}

	@Serdeable
	record SomeModel(List<Something> specificList, List<String> genericList) {
	}

	@Singleton
	static class ListSomethingDeserializer implements Deserializer<List<Something>> {
		@Override
		public @Nullable List<Something> deserialize(@NonNull Decoder decoder, @NonNull DecoderContext context, io.micronaut.core.type.@NonNull Argument<? super List<Something>> type) throws IOException {
			String stringValue = decoder.decodeString();
			return java.util.Arrays.stream(stringValue.split("\\|"))
				.map(Something::new)
				.toList();
		}
	}

	@Singleton
	static class ListSomethingSerializer implements Serializer<List<Something>> {
		@Override
		public void serialize(@NonNull Encoder encoder, @NonNull EncoderContext context, @NonNull Argument<? extends List<Something>> type, @NonNull List<Something> value) throws IOException {
			encoder.encodeString(value.stream().map(Something::s).collect(Collectors.joining("|")));
		}
	}

	@Test
	void test() throws IOException {
		String json = "{\"specificList\":\"a|b|c\",\"genericList\":[\"a\",\"b\",\"c\"]}";
		SomeModel model = objectMapper.readValue(json, SomeModel.class);
		Assertions.assertEquals(new SomeModel(List.of(new Something("a"), new Something("b"), new Something("c")), List.of("a", "b", "c")), model);
		Assertions.assertEquals(json, objectMapper.writeValueAsString(model));
		Assertions.assertEquals("[\"a\",\"b\",\"c\"]", objectMapper.writeValueAsString(List.of("a", "b", "c")));
	}
}
java.lang.ClassCastException: class java.lang.String cannot be cast to class x.SerdeTest$Something (java.lang.String is in module java.base of loader 'bootstrap'; x.SerdeTest$Something is in unnamed module of loader 'app')

	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:722)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
	at x.SerdeTest$ListSomethingSerializer.serialize(SerdeTest.java:50)
	at x.SerdeTest$ListSomethingSerializer.serialize(SerdeTest.java:46)
	at io.micronaut.serde.jackson.JacksonJsonMapper.writeValue(JacksonJsonMapper.java:246)
	at io.micronaut.serde.jackson.JacksonJsonMapper.writeValue0(JacksonJsonMapper.java:228)
	at io.micronaut.serde.jackson.JacksonJsonMapper.writeValue0(JacksonJsonMapper.java:223)
	at io.micronaut.serde.jackson.JacksonJsonMapper.writeValueAsBytes(JacksonJsonMapper.java:343)
	at io.micronaut.serde.ObjectMapper.writeValueAsString(ObjectMapper.java:43)
	at x.SerdeTest.test(SerdeTest.java:60)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at io.micronaut.test.extensions.junit5.MicronautJunit5Extension$2.proceed(MicronautJunit5Extension.java:142)
	at io.micronaut.test.extensions.AbstractMicronautExtension.interceptEach(AbstractMicronautExtension.java:162)
	at io.micronaut.test.extensions.AbstractMicronautExtension.interceptTest(AbstractMicronautExtension.java:119)
	at io.micronaut.test.extensions.junit5.MicronautJunit5Extension.interceptTestMethod(MicronautJunit5Extension.java:129)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

Environment Information

Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Java version: 21.0.8, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-21-openjdk
OS name: "linux", version: "6.17.1-300.fc43.x86_64", arch: "amd64", family: "unix"

Example Application

No response

Version

4.9.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions