Skip to content

Commit be78dd3

Browse files
SentryManrbygrave
andauthored
Add something like @JsonSerialize (#282)
* start * add tests * rename and javadoc * catch an edge case * Format, extract methods * Remove isGeneric() from CustomAdapter --------- Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>
1 parent f46f7bc commit be78dd3

File tree

15 files changed

+259
-31
lines changed

15 files changed

+259
-31
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.example.other.custom.serializer;
2+
3+
import java.math.BigDecimal;
4+
5+
import io.avaje.jsonb.Json;
6+
7+
@Json
8+
public record CustomExample(
9+
@Json.Serializer(MoneySerializer.class)
10+
BigDecimal amountOwed,
11+
BigDecimal somethingElse) {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.example.other.custom.serializer;
2+
3+
import java.math.BigDecimal;
4+
import java.math.RoundingMode;
5+
6+
import io.avaje.jsonb.CustomAdapter;
7+
import io.avaje.jsonb.JsonAdapter;
8+
import io.avaje.jsonb.JsonReader;
9+
import io.avaje.jsonb.JsonWriter;
10+
import io.avaje.jsonb.Jsonb;
11+
12+
@CustomAdapter(global = false)
13+
public class MoneySerializer implements JsonAdapter<BigDecimal> {
14+
15+
public MoneySerializer(Jsonb jsonb) {}
16+
17+
@Override
18+
public BigDecimal fromJson(JsonReader reader) {
19+
return reader.readDecimal().setScale(2, RoundingMode.DOWN);
20+
}
21+
22+
@Override
23+
public void toJson(JsonWriter writer, BigDecimal value) {
24+
writer.value(value.setScale(2, RoundingMode.DOWN));
25+
}
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.example.other.custom.serializer;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.math.BigDecimal;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import io.avaje.jsonb.JsonType;
10+
import io.avaje.jsonb.Jsonb;
11+
12+
class TestSelectiveSerializer {
13+
14+
Jsonb jsonb = Jsonb.builder().build();
15+
JsonType<CustomExample> jsonType = jsonb.type(CustomExample.class);
16+
17+
@Test
18+
void toFromJson() {
19+
final var bean = new CustomExample(new BigDecimal("100.95630"), new BigDecimal("100.95630"));
20+
21+
final String asJson = jsonType.toJson(bean);
22+
assertThat(asJson).isEqualTo("{\"amountOwed\":100.95,\"somethingElse\":100.95630}");
23+
24+
final var fromJson = jsonType.fromJson(asJson);
25+
assertThat(fromJson.amountOwed()).isEqualTo(new BigDecimal("100.95"));
26+
assertThat(fromJson.somethingElse()).isEqualTo(new BigDecimal("100.95630"));
27+
assertThat(fromJson).isNotEqualTo(bean);
28+
}
29+
}

jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public void writeFields(Append writer) {
234234
uniqueTypes.add("String");
235235
}
236236
for (final FieldReader allField : allFields) {
237-
if (allField.include() && !allField.isRaw() && uniqueTypes.add(allField.adapterShortType())) {
237+
if (includeField(allField, uniqueTypes)) {
238238
allField.writeField(writer);
239239
}
240240
}
@@ -247,6 +247,17 @@ public void writeFields(Append writer) {
247247
writer.eol();
248248
}
249249

250+
private static boolean includeField(FieldReader allField, Set<String> uniqueTypes) {
251+
return allField.include()
252+
&& !allField.isRaw()
253+
&& includeFieldUniqueType(allField, uniqueTypes);
254+
}
255+
256+
private static boolean includeFieldUniqueType(FieldReader allField, Set<String> uniqueTypes) {
257+
return allField.hasCustomSerializer() && uniqueTypes.add(allField.adapterFieldName())
258+
|| !allField.hasCustomSerializer() && uniqueTypes.add(allField.adapterShortType());
259+
}
260+
250261
@Override
251262
public void writeConstructor(Append writer) {
252263
if (hasRaw) {
@@ -259,7 +270,7 @@ public void writeConstructor(Append writer) {
259270
uniqueTypes.add("String");
260271
}
261272
for (final FieldReader allField : allFields) {
262-
if (allField.include() && !allField.isRaw() && uniqueTypes.add(allField.adapterShortType())) {
273+
if (includeField(allField, uniqueTypes)) {
263274
if (hasSubTypes) {
264275
final var isCommonDiffType =
265276
allFields.stream()

jsonb-generator/src/main/java/io/avaje/jsonb/generator/ComponentMetaData.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.avaje.jsonb.generator;
22

3+
import static java.util.function.Predicate.not;
4+
35
import java.util.*;
46

57
final class ComponentMetaData {
68

79
private final List<String> allTypes = new ArrayList<>();
810
private final List<String> factoryTypes = new ArrayList<>();
11+
private final List<String> withTypes = new ArrayList<>();
912
private String fullName;
1013

1114
@Override
@@ -25,13 +28,20 @@ boolean contains(String type) {
2528
}
2629

2730
void add(String type) {
28-
allTypes.add(type);
31+
Optional.ofNullable(APContext.typeElement(type))
32+
.flatMap(CustomAdapterPrism::getOptionalOn)
33+
.filter(not(CustomAdapterPrism::global))
34+
.ifPresentOrElse(p -> withTypes.add(type), () -> allTypes.add(type));
2935
}
3036

3137
void addFactory(String fullName) {
3238
factoryTypes.add(fullName);
3339
}
3440

41+
public void addWithType(String type) {
42+
withTypes.add(type);
43+
}
44+
3545
void setFullName(String fullName) {
3646
this.fullName = fullName;
3747
}
@@ -59,6 +69,10 @@ List<String> allFactories() {
5969
return factoryTypes;
6070
}
6171

72+
List<String> withTypes() {
73+
return withTypes;
74+
}
75+
6276
/**
6377
* Return the package imports for the JsonAdapters and related types.
6478
*/
@@ -73,6 +87,7 @@ Collection<String> allImports() {
7387
}
7488

7589
packageImports.addAll(factoryTypes);
90+
packageImports.addAll(withTypes);
7691
return packageImports;
7792
}
7893

jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldProperty.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import javax.lang.model.type.TypeMirror;
44
import java.util.ArrayList;
55
import java.util.List;
6+
import java.util.Optional;
67
import java.util.Set;
78

89
final class FieldProperty {
@@ -25,20 +26,35 @@ final class FieldProperty {
2526
private int position;
2627
private MethodReader getter;
2728
private MethodReader setter;
29+
private final Optional<String> customSerializer;
2830

2931
FieldProperty(MethodReader methodReader) {
30-
this(methodReader.returnType(), false, false, new ArrayList<>(), false, methodReader.getName());
31-
}
32-
33-
FieldProperty(TypeMirror asType, boolean raw, boolean unmapped, List<String> genericTypeParams,
34-
boolean publicField, String fieldName) {
32+
this(
33+
methodReader.returnType(),
34+
false,
35+
false,
36+
new ArrayList<>(),
37+
false,
38+
methodReader.getName(),
39+
SerializerPrism.getOptionalOn(methodReader.element()).map(SerializerPrism::value));
40+
}
41+
42+
FieldProperty(
43+
TypeMirror asType,
44+
boolean raw,
45+
boolean unmapped,
46+
List<String> genericTypeParams,
47+
boolean publicField,
48+
String fieldName,
49+
Optional<TypeMirror> customSerializer) {
3550
this.raw = raw;
3651
this.unmapped = unmapped;
3752
this.publicField = publicField;
3853
this.fieldName = fieldName;
3954
this.rawType = Util.trimAnnotations(asType.toString());
4055
this.optional = rawType.startsWith("java.util.Optional");
4156
this.genericTypeParams = genericTypeParams;
57+
this.customSerializer = customSerializer.map(TypeMirror::toString);
4258

4359
if (raw) {
4460
genericType = GenericType.parse("java.lang.String");
@@ -56,7 +72,11 @@ final class FieldProperty {
5672
boolean primitive = PrimitiveUtil.isPrimitive(shortType);
5773
defaultValue = !primitive ? "null" : PrimitiveUtil.defaultValue(shortType);
5874
adapterShortType = initAdapterShortType(shortType);
59-
adapterFieldName = (primitive && !optional ? "p" : "") + initShortName();
75+
adapterFieldName =
76+
this.customSerializer
77+
.map(Util::shortType)
78+
.map(s -> Character.toLowerCase(s.charAt(0)) + s.substring(1))
79+
.orElse((primitive && !optional ? "p" : "") + initShortName());
6080
}
6181
}
6282

@@ -160,6 +180,7 @@ private boolean nameHasIsPrefix() {
160180
}
161181

162182
void addImports(Set<String> importTypes) {
183+
customSerializer.ifPresent(t -> importTypes.add(t.toString()));
163184
if (unmapped) {
164185
importTypes.add("java.util.*");
165186
}
@@ -190,7 +211,9 @@ void writeConstructor(Append writer) {
190211
if (raw) {
191212
writer.append(" this.%s = jsonb.rawAdapter();", adapterFieldName).eol();
192213
} else {
193-
writer.append(" this.%s = jsonb.adapter(%s);", adapterFieldName, asTypeDeclaration()).eol();
214+
customSerializer.ifPresentOrElse(
215+
c -> writer.append(" this.%s = jsonb.customAdapter(%s.class);", adapterFieldName, Util.shortType(c)).eol(),
216+
() -> writer.append(" this.%s = jsonb.adapter(%s);", adapterFieldName, asTypeDeclaration()).eol());
194217
}
195218
}
196219

jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldReader.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import javax.lang.model.element.Element;
44
import javax.lang.model.element.ExecutableElement;
5-
import javax.lang.model.element.Modifier;
65
import javax.lang.model.element.VariableElement;
6+
77
import java.util.*;
88

99
final class FieldReader {
@@ -16,6 +16,7 @@ final class FieldReader {
1616
private boolean deserialize;
1717
private final boolean unmapped;
1818
private final boolean raw;
19+
private final boolean hasCustomSerializer;
1920

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

54-
this.property = new FieldProperty(type, raw, unmapped, genericTypeParams, publicField, fieldName);
55-
this.propertyName =
56-
PropertyPrism.getOptionalOn(element)
57-
.map(PropertyPrism::value)
58-
.map(Util::escapeQuotes)
59-
.orElse(namingConvention.from(fieldName));
55+
final var customSerializer = SerializerPrism.getOptionalOn(element).map(SerializerPrism::value);
56+
this.hasCustomSerializer = customSerializer.isPresent();
57+
this.property =
58+
new FieldProperty(
59+
type,
60+
raw,
61+
unmapped,
62+
genericTypeParams,
63+
publicField,
64+
fieldName,
65+
customSerializer);
66+
this.propertyName = PropertyPrism.getOptionalOn(element)
67+
.map(PropertyPrism::value)
68+
.map(Util::escapeQuotes)
69+
.orElse(namingConvention.from(fieldName));
6070

6171
final PropertyIgnoreReader ignoreReader = new PropertyIgnoreReader(element, propertyName);
6272
this.serialize = !isParam && ignoreReader.serialize();
@@ -167,6 +177,10 @@ boolean isPublicField() {
167177
return property.isPublicField();
168178
}
169179

180+
boolean hasCustomSerializer() {
181+
return hasCustomSerializer;
182+
}
183+
170184
void writeDebug(Append writer) {
171185
writer.append(" // %s [%s] name:%s", property.fieldName(), property.rawType(), propertyName);
172186
if (!serialize) {

jsonb-generator/src/main/java/io/avaje/jsonb/generator/SimpleComponentWriter.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import java.io.IOException;
77
import java.io.Writer;
8+
import java.util.ArrayList;
89
import java.util.List;
910
import java.util.Set;
1011
import java.util.TreeSet;
@@ -61,8 +62,12 @@ void writeMetaInf() throws IOException {
6162
private void writeRegister() {
6263
writer.append(" @Override").eol();
6364
writer.append(" public void register(Jsonb.Builder builder) {").eol();
64-
final List<String> strings = metaData.allFactories();
65-
for (final String adapterFullName : strings) {
65+
66+
for (final String adapterFullName : metaData.withTypes()) {
67+
final String adapterShortName = Util.shortName(adapterFullName);
68+
writer.append(" builder.add(%s.class, %s::new);", adapterShortName, adapterShortName).eol();
69+
}
70+
for (final String adapterFullName : metaData.allFactories()) {
6671
final String adapterShortName = Util.shortName(adapterFullName);
6772
writer.append(" builder.add(%s.FACTORY);", adapterShortName).eol();
6873
}
@@ -89,7 +94,8 @@ private void writeClassStart() {
8994
writer.append("})").eol();
9095
}
9196
writer.append("@MetaData({");
92-
final List<String> all = metaData.all();
97+
final List<String> all = new ArrayList<>(metaData.all());
98+
all.addAll(metaData.withTypes());
9399
writeMetaDataEntry(all);
94100
writer.append("})").eol();
95101

jsonb-generator/src/main/java/io/avaje/jsonb/generator/package-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@GeneratePrism(io.avaje.jsonb.Json.SubType.class)
1313
@GeneratePrism(io.avaje.jsonb.Json.Unmapped.class)
1414
@GeneratePrism(io.avaje.jsonb.Json.Value.class)
15+
@GeneratePrism(io.avaje.jsonb.Json.Serializer.class)
1516
@GeneratePrism(io.avaje.jsonb.spi.MetaData.class)
1617
@GeneratePrism(io.avaje.jsonb.spi.MetaData.Factory.class)
1718
package io.avaje.jsonb.generator;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.avaje.jsonb.generator.models.valid;
2+
3+
import java.math.BigDecimal;
4+
5+
import io.avaje.jsonb.CustomAdapter;
6+
import io.avaje.jsonb.JsonAdapter;
7+
import io.avaje.jsonb.JsonReader;
8+
import io.avaje.jsonb.JsonWriter;
9+
import io.avaje.jsonb.Jsonb;
10+
11+
@CustomAdapter(global = false)
12+
public class MoneySerializer implements JsonAdapter<BigDecimal> {
13+
14+
public MoneySerializer(Jsonb jsonb) {}
15+
16+
@Override
17+
public void toJson(JsonWriter writer, BigDecimal value) {}
18+
19+
@Override
20+
public BigDecimal fromJson(JsonReader reader) {
21+
22+
return null;
23+
}
24+
}

0 commit comments

Comments
 (0)