Using Jackson Databind 2.13.5, annotating a POJO with @JsonDeserialize:
@Getter
@Builder
@ToString
@JsonDeserialize(builder=Person.PersonBuilder.class)
static class Person {
private final String name;
private final List<Pet> pets;
}
@Getter
@Builder
@ToString
static class Pet {
private final String name;
private final String type;
}
takes priority over an explicitly configured deserializer in a mapper:
String serializedPersons = """
name,pets
Ali,"[{'Cutesy','cat'}, {'Tootsie','cat'}]"
Jan,"[{'Mr. Bubbles','dog'}, {'Lilly','cat'}]"
Tom,"[{'Fester','dog'}, {'Wednesday','cat'}]"
Zoe,"[{'Jaeger','dog'}, {'Deira','cat'}]"
""";
CsvMapper mapper = new CsvMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Person.class, new PersonSerializer());
module.addDeserializer(Person.class, new PersonDeserializer());
mapper.registerModule(module);
CsvSchema schema = mapper.schemaFor(Person.class).withHeader();
ObjectReader reader = mapper.readerFor(Person.class).with(schema);
MappingIterator<Person> i = reader.readValues(serializedPersons);
while (i.hasNext()) {
System.out.println("Person: " + i.next());
}
It seems like explicitly configured serializers & deserializers on the mappers should take precedence over others that are discovered.
In my case, I have network marshalled objects that should be using annotated builder, and CLI applications that should be using their own explicitly configured deserializers. I wrote a question on SO before I found the problem.
public class JacksonTest {
@Test
public void writeJson() throws JsonProcessingException {
var mapper = new ObjectMapper();
var writer = mapper.writer();
System.out.println(writer.writeValueAsString(_single()));
System.out.println(writer.writeValueAsString(_multiple()));
}
@Test
public void writeJsonObjectsIndividually() throws IOException {
var mapper = new ObjectMapper();
var writer = new StringWriter();
try (var seqWriter = mapper.writerFor(Person.class).writeValues(writer)) {
seqWriter.write(_single());
seqWriter.writeAll(_multiple());
}
System.out.println(writer.toString());
}
@Test
public void writePersonToCsv() throws IOException {
CsvMapper mapper = new CsvMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Person.class, new PersonSerializer());
module.addDeserializer(Person.class, new PersonDeserializer());
mapper.registerModule(module);
var writer = new StringWriter();
CsvSchema schema = mapper.schemaFor(Person.class)
.withHeader();
try (var seqWriter = mapper.writerWithSchemaFor(Person.class).with(schema).writeValues(writer)) {
seqWriter.write(_single());
seqWriter.writeAll(_multiple());
}
System.out.println(writer.toString());
}
@Test
public void deserializePets() {
String serializedPets = "[{'Mr. Bubbles','dog'}, {'Lilly','cat'}]";
List<Pet> pets = PersonDeserializer.deserialize(serializedPets);
assertThat(pets).hasSize(2);
}
@Test
public void readPersonFromCsv() throws IOException {
String serializedPersons = """
name,pets
Ali,"[{'Cutesy','cat'}, {'Tootsie','cat'}]"
Jan,"[{'Mr. Bubbles','dog'}, {'Lilly','cat'}]"
Tom,"[{'Fester','dog'}, {'Wednesday','cat'}]"
Zoe,"[{'Jaeger','dog'}, {'Deira','cat'}]"
""";
CsvMapper mapper = new CsvMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Person.class, new PersonSerializer());
module.addDeserializer(Person.class, new PersonDeserializer());
mapper.registerModule(module);
CsvSchema schema = mapper.schemaFor(Person.class).withHeader();
ObjectReader reader = mapper.readerFor(Person.class).with(schema);
MappingIterator<Person> i = reader.readValues(serializedPersons);
while (i.hasNext()) {
System.out.println("Person: " + i.next());
}
}
@Getter
@Builder
@ToString
//This will take precedence over mapper registered deserializers
@JsonDeserialize(builder=Person.PersonBuilder.class)
static class Person {
private final String name;
private final List<Pet> pets;
}
@Getter
@Builder
@ToString
static class Pet {
private final String name;
private final String type;
}
static class PersonSerializer extends StdSerializer<Person> {
private static final long serialVersionUID = 1L;
public PersonSerializer() {
this(Person.class);
}
public PersonSerializer(Class<Person> type) {
super(type);
}
@Override
public void serialize(Person value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
jgen.writeStringField("pets", _toString(value.getPets()));
jgen.writeEndObject();
}
private String _toString(List<Pet> pets) {
return Arrays.toString(pets.stream()
.map(PersonSerializer::serialize)
.toArray(String[]::new));
}
private static String serialize(Pet pet) {
return String.format("{'%s','%s'}", pet.name, pet.type);
}
}
static class PersonDeserializer extends StdDeserializer<Person> {
private static final long serialVersionUID = 1L;
public PersonDeserializer() {
this(Person.class);
}
public PersonDeserializer(Class<Person> type) {
super(type);
}
@Override
public Person deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
String name = node.get("name").asText();
String petsSerialized = node.get("pets").asText();
List<Pet> pets = deserialize(petsSerialized);
return Person.builder().name(name).pets(pets).build();
}
private static List<Pet> deserialize(String serializedPets) {
return Arrays.stream(serializedPets.replaceAll("^.\\{?|}?.$", "").split("},\s*\\{"))
.map(inner -> Arrays.stream(inner.replaceAll("^.'?|'?.$", "").split("','")).toList())
.map(l -> deserialize(l))
.collect(Collectors.toList());
}
private static Pet deserialize(List<String> l) {
return Pet.builder().name(l.get(0)).type(l.get(1)).build();
}
}
private static Person _single() {
return Person.builder().name("Jan").pets(List.of(Pet.builder().name("Mr. Bubbles").type("dog").build(), Pet.builder().name("Lilly").type("cat").build())).build();
}
private static Person[] _multiple() {
return List.of(_single(), _single(), _single()).toArray(Person[]::new);
}
}
Using Jackson Databind 2.13.5, annotating a POJO with
@JsonDeserialize
:takes priority over an explicitly configured deserializer in a mapper:
It seems like explicitly configured serializers & deserializers on the mappers should take precedence over others that are discovered.
In my case, I have network marshalled objects that should be using annotated builder, and CLI applications that should be using their own explicitly configured deserializers. I wrote a question on SO before I found the problem.