Skip to content

Commit

Permalink
Add something like @JsonSerialize (#282)
Browse files Browse the repository at this point in the history
* start

* add tests

* rename and javadoc

* catch an edge case

* Format, extract methods

* Remove isGeneric() from CustomAdapter

---------

Co-authored-by: Rob Bygrave <[email protected]>
  • Loading branch information
SentryMan and rbygrave authored Aug 5, 2024
1 parent f46f7bc commit be78dd3
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.example.other.custom.serializer;

import java.math.BigDecimal;

import io.avaje.jsonb.Json;

@Json
public record CustomExample(
@Json.Serializer(MoneySerializer.class)
BigDecimal amountOwed,
BigDecimal somethingElse) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.example.other.custom.serializer;

import java.math.BigDecimal;
import java.math.RoundingMode;

import io.avaje.jsonb.CustomAdapter;
import io.avaje.jsonb.JsonAdapter;
import io.avaje.jsonb.JsonReader;
import io.avaje.jsonb.JsonWriter;
import io.avaje.jsonb.Jsonb;

@CustomAdapter(global = false)
public class MoneySerializer implements JsonAdapter<BigDecimal> {

public MoneySerializer(Jsonb jsonb) {}

@Override
public BigDecimal fromJson(JsonReader reader) {
return reader.readDecimal().setScale(2, RoundingMode.DOWN);
}

@Override
public void toJson(JsonWriter writer, BigDecimal value) {
writer.value(value.setScale(2, RoundingMode.DOWN));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.example.other.custom.serializer;

import static org.assertj.core.api.Assertions.assertThat;

import java.math.BigDecimal;

import org.junit.jupiter.api.Test;

import io.avaje.jsonb.JsonType;
import io.avaje.jsonb.Jsonb;

class TestSelectiveSerializer {

Jsonb jsonb = Jsonb.builder().build();
JsonType<CustomExample> jsonType = jsonb.type(CustomExample.class);

@Test
void toFromJson() {
final var bean = new CustomExample(new BigDecimal("100.95630"), new BigDecimal("100.95630"));

final String asJson = jsonType.toJson(bean);
assertThat(asJson).isEqualTo("{\"amountOwed\":100.95,\"somethingElse\":100.95630}");

final var fromJson = jsonType.fromJson(asJson);
assertThat(fromJson.amountOwed()).isEqualTo(new BigDecimal("100.95"));
assertThat(fromJson.somethingElse()).isEqualTo(new BigDecimal("100.95630"));
assertThat(fromJson).isNotEqualTo(bean);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public void writeFields(Append writer) {
uniqueTypes.add("String");
}
for (final FieldReader allField : allFields) {
if (allField.include() && !allField.isRaw() && uniqueTypes.add(allField.adapterShortType())) {
if (includeField(allField, uniqueTypes)) {
allField.writeField(writer);
}
}
Expand All @@ -247,6 +247,17 @@ public void writeFields(Append writer) {
writer.eol();
}

private static boolean includeField(FieldReader allField, Set<String> uniqueTypes) {
return allField.include()
&& !allField.isRaw()
&& includeFieldUniqueType(allField, uniqueTypes);
}

private static boolean includeFieldUniqueType(FieldReader allField, Set<String> uniqueTypes) {
return allField.hasCustomSerializer() && uniqueTypes.add(allField.adapterFieldName())
|| !allField.hasCustomSerializer() && uniqueTypes.add(allField.adapterShortType());
}

@Override
public void writeConstructor(Append writer) {
if (hasRaw) {
Expand All @@ -259,7 +270,7 @@ public void writeConstructor(Append writer) {
uniqueTypes.add("String");
}
for (final FieldReader allField : allFields) {
if (allField.include() && !allField.isRaw() && uniqueTypes.add(allField.adapterShortType())) {
if (includeField(allField, uniqueTypes)) {
if (hasSubTypes) {
final var isCommonDiffType =
allFields.stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.avaje.jsonb.generator;

import static java.util.function.Predicate.not;

import java.util.*;

final class ComponentMetaData {

private final List<String> allTypes = new ArrayList<>();
private final List<String> factoryTypes = new ArrayList<>();
private final List<String> withTypes = new ArrayList<>();
private String fullName;

@Override
Expand All @@ -25,13 +28,20 @@ boolean contains(String type) {
}

void add(String type) {
allTypes.add(type);
Optional.ofNullable(APContext.typeElement(type))
.flatMap(CustomAdapterPrism::getOptionalOn)
.filter(not(CustomAdapterPrism::global))
.ifPresentOrElse(p -> withTypes.add(type), () -> allTypes.add(type));
}

void addFactory(String fullName) {
factoryTypes.add(fullName);
}

public void addWithType(String type) {
withTypes.add(type);
}

void setFullName(String fullName) {
this.fullName = fullName;
}
Expand Down Expand Up @@ -59,6 +69,10 @@ List<String> allFactories() {
return factoryTypes;
}

List<String> withTypes() {
return withTypes;
}

/**
* Return the package imports for the JsonAdapters and related types.
*/
Expand All @@ -73,6 +87,7 @@ Collection<String> allImports() {
}

packageImports.addAll(factoryTypes);
packageImports.addAll(withTypes);
return packageImports;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import javax.lang.model.type.TypeMirror;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

final class FieldProperty {
Expand All @@ -25,20 +26,35 @@ final class FieldProperty {
private int position;
private MethodReader getter;
private MethodReader setter;
private final Optional<String> customSerializer;

FieldProperty(MethodReader methodReader) {
this(methodReader.returnType(), false, false, new ArrayList<>(), false, methodReader.getName());
}

FieldProperty(TypeMirror asType, boolean raw, boolean unmapped, List<String> genericTypeParams,
boolean publicField, String fieldName) {
this(
methodReader.returnType(),
false,
false,
new ArrayList<>(),
false,
methodReader.getName(),
SerializerPrism.getOptionalOn(methodReader.element()).map(SerializerPrism::value));
}

FieldProperty(
TypeMirror asType,
boolean raw,
boolean unmapped,
List<String> genericTypeParams,
boolean publicField,
String fieldName,
Optional<TypeMirror> customSerializer) {
this.raw = raw;
this.unmapped = unmapped;
this.publicField = publicField;
this.fieldName = fieldName;
this.rawType = Util.trimAnnotations(asType.toString());
this.optional = rawType.startsWith("java.util.Optional");
this.genericTypeParams = genericTypeParams;
this.customSerializer = customSerializer.map(TypeMirror::toString);

if (raw) {
genericType = GenericType.parse("java.lang.String");
Expand All @@ -56,7 +72,11 @@ final class FieldProperty {
boolean primitive = PrimitiveUtil.isPrimitive(shortType);
defaultValue = !primitive ? "null" : PrimitiveUtil.defaultValue(shortType);
adapterShortType = initAdapterShortType(shortType);
adapterFieldName = (primitive && !optional ? "p" : "") + initShortName();
adapterFieldName =
this.customSerializer
.map(Util::shortType)
.map(s -> Character.toLowerCase(s.charAt(0)) + s.substring(1))
.orElse((primitive && !optional ? "p" : "") + initShortName());
}
}

Expand Down Expand Up @@ -160,6 +180,7 @@ private boolean nameHasIsPrefix() {
}

void addImports(Set<String> importTypes) {
customSerializer.ifPresent(t -> importTypes.add(t.toString()));
if (unmapped) {
importTypes.add("java.util.*");
}
Expand Down Expand Up @@ -190,7 +211,9 @@ void writeConstructor(Append writer) {
if (raw) {
writer.append(" this.%s = jsonb.rawAdapter();", adapterFieldName).eol();
} else {
writer.append(" this.%s = jsonb.adapter(%s);", adapterFieldName, asTypeDeclaration()).eol();
customSerializer.ifPresentOrElse(
c -> writer.append(" this.%s = jsonb.customAdapter(%s.class);", adapterFieldName, Util.shortType(c)).eol(),
() -> writer.append(" this.%s = jsonb.adapter(%s);", adapterFieldName, asTypeDeclaration()).eol());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;

import java.util.*;

final class FieldReader {
Expand All @@ -16,6 +16,7 @@ final class FieldReader {
private boolean deserialize;
private final boolean unmapped;
private final boolean raw;
private final boolean hasCustomSerializer;

private final List<String> aliases = new ArrayList<>();
private boolean isSubTypeField;
Expand Down Expand Up @@ -51,12 +52,21 @@ final class FieldReader {
final var publicField = !isMethod && !isParam && Util.isPublic(element);
final var type = isMethod ? ((ExecutableElement) element).getReturnType() : element.asType();

this.property = new FieldProperty(type, raw, unmapped, genericTypeParams, publicField, fieldName);
this.propertyName =
PropertyPrism.getOptionalOn(element)
.map(PropertyPrism::value)
.map(Util::escapeQuotes)
.orElse(namingConvention.from(fieldName));
final var customSerializer = SerializerPrism.getOptionalOn(element).map(SerializerPrism::value);
this.hasCustomSerializer = customSerializer.isPresent();
this.property =
new FieldProperty(
type,
raw,
unmapped,
genericTypeParams,
publicField,
fieldName,
customSerializer);
this.propertyName = PropertyPrism.getOptionalOn(element)
.map(PropertyPrism::value)
.map(Util::escapeQuotes)
.orElse(namingConvention.from(fieldName));

final PropertyIgnoreReader ignoreReader = new PropertyIgnoreReader(element, propertyName);
this.serialize = !isParam && ignoreReader.serialize();
Expand Down Expand Up @@ -167,6 +177,10 @@ boolean isPublicField() {
return property.isPublicField();
}

boolean hasCustomSerializer() {
return hasCustomSerializer;
}

void writeDebug(Append writer) {
writer.append(" // %s [%s] name:%s", property.fieldName(), property.rawType(), propertyName);
if (!serialize) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
Expand Down Expand Up @@ -61,8 +62,12 @@ void writeMetaInf() throws IOException {
private void writeRegister() {
writer.append(" @Override").eol();
writer.append(" public void register(Jsonb.Builder builder) {").eol();
final List<String> strings = metaData.allFactories();
for (final String adapterFullName : strings) {

for (final String adapterFullName : metaData.withTypes()) {
final String adapterShortName = Util.shortName(adapterFullName);
writer.append(" builder.add(%s.class, %s::new);", adapterShortName, adapterShortName).eol();
}
for (final String adapterFullName : metaData.allFactories()) {
final String adapterShortName = Util.shortName(adapterFullName);
writer.append(" builder.add(%s.FACTORY);", adapterShortName).eol();
}
Expand All @@ -89,7 +94,8 @@ private void writeClassStart() {
writer.append("})").eol();
}
writer.append("@MetaData({");
final List<String> all = metaData.all();
final List<String> all = new ArrayList<>(metaData.all());
all.addAll(metaData.withTypes());
writeMetaDataEntry(all);
writer.append("})").eol();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@GeneratePrism(io.avaje.jsonb.Json.SubType.class)
@GeneratePrism(io.avaje.jsonb.Json.Unmapped.class)
@GeneratePrism(io.avaje.jsonb.Json.Value.class)
@GeneratePrism(io.avaje.jsonb.Json.Serializer.class)
@GeneratePrism(io.avaje.jsonb.spi.MetaData.class)
@GeneratePrism(io.avaje.jsonb.spi.MetaData.Factory.class)
package io.avaje.jsonb.generator;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.avaje.jsonb.generator.models.valid;

import java.math.BigDecimal;

import io.avaje.jsonb.CustomAdapter;
import io.avaje.jsonb.JsonAdapter;
import io.avaje.jsonb.JsonReader;
import io.avaje.jsonb.JsonWriter;
import io.avaje.jsonb.Jsonb;

@CustomAdapter(global = false)
public class MoneySerializer implements JsonAdapter<BigDecimal> {

public MoneySerializer(Jsonb jsonb) {}

@Override
public void toJson(JsonWriter writer, BigDecimal value) {}

@Override
public BigDecimal fromJson(JsonReader reader) {

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.avaje.jsonb.generator.models.valid;

import java.math.BigDecimal;

import io.avaje.jsonb.Json;

@Json
public class SerializerTest {

@Json.Serializer(MoneySerializer.class)
BigDecimal amount;

@Json.Serializer(MoneySerializer.class)
BigDecimal amountReturned;

BigDecimal somethingElse;

@Json.Property("methodCustom")
@Json.Serializer(MoneySerializer.class)
BigDecimal methodCustom() {
return null;
}
}
Loading

0 comments on commit be78dd3

Please sign in to comment.