Skip to content

Commit d71e3d5

Browse files
authored
Merge pull request #46 from avaje/feature/case-insensitive-keys
ENH: Add support for case-insensitive keys on deserialization
2 parents 1b5c0da + 7585037 commit d71e3d5

File tree

6 files changed

+73
-12
lines changed

6 files changed

+73
-12
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.example.customer.caseinsensitive;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
@Json(caseInsensitiveKeys = true)
6+
public record ICaseContact(int id, String firstName, String lastName) {
7+
8+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.example.customer.caseinsensitive;
2+
3+
import io.avaje.jsonb.JsonType;
4+
import io.avaje.jsonb.Jsonb;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
class ICaseContactTest {
10+
11+
Jsonb jsonb = Jsonb.builder().build();
12+
13+
@Test
14+
void toJsonFromJson() {
15+
ICaseContact bean = new ICaseContact(42, "first", "last");
16+
String asJson = jsonb.toJson(bean);
17+
assertThat(asJson).isEqualTo("{\"id\":42,\"firstName\":\"first\",\"lastName\":\"last\"}");
18+
19+
JsonType<ICaseContact> type = jsonb.type(ICaseContact.class);
20+
ICaseContact fromJson = type.fromJson(asJson);
21+
22+
assertThat(fromJson).isEqualTo(bean);
23+
assertThat(type.fromJson("{\"id\":42,\"FIRSTNAME\":\"first\",\"lastname\":\"last\"}")).isEqualTo(bean);
24+
assertThat(type.fromJson("{\"id\":42,\"FirstName\":\"first\",\"LastName\":\"last\"}")).isEqualTo(bean);
25+
}
26+
}

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class BeanReader {
2323
private final TypeReader typeReader;
2424
private final String typeProperty;
2525
private final boolean nonAccessibleField;
26+
private final boolean caseInsensitiveKeys;
2627
private FieldReader unmappedField;
2728
private boolean hasRaw;
2829

@@ -33,7 +34,7 @@ class BeanReader {
3334
NamingConventionReader ncReader = new NamingConventionReader(beanType);
3435
this.namingConvention = ncReader.get();
3536
this.typeProperty = ncReader.typeProperty();
36-
37+
this.caseInsensitiveKeys = ncReader.isCaseInsensitiveKeys();
3738
this.typeReader = new TypeReader(beanType, context, namingConvention);
3839
typeReader.process();
3940
this.nonAccessibleField = typeReader.nonAccessibleField();
@@ -49,6 +50,7 @@ public BeanReader(TypeElement beanType, TypeElement mixInElement, ProcessingCont
4950
final NamingConventionReader ncReader = new NamingConventionReader(beanType);
5051
this.namingConvention = ncReader.get();
5152
this.typeProperty = ncReader.typeProperty();
53+
this.caseInsensitiveKeys = ncReader.isCaseInsensitiveKeys();
5254
this.typeReader = new TypeReader(beanType, mixInElement, context, namingConvention);
5355
typeReader.process();
5456
this.nonAccessibleField = typeReader.nonAccessibleField();
@@ -354,22 +356,28 @@ private void writeFromJsonSwitch(Append writer, boolean defaultConstructor, Stri
354356
writer.append(" reader.beginObject();").eol();
355357
writer.append(" reader.names(names);").eol();
356358
writer.append(" while (reader.hasNextField()) {").eol();
357-
writer.append(" String fieldName = reader.nextField();").eol();
359+
if (caseInsensitiveKeys) {
360+
writer.append(" final String origFieldName = reader.nextField();").eol();
361+
writer.append(" final String fieldName = origFieldName.toLowerCase();").eol();
362+
} else {
363+
writer.append(" final String fieldName = reader.nextField();").eol();
364+
}
358365
writer.append(" switch (fieldName) {").eol();
359366
if (hasSubTypes) {
360-
writer.append(" case \"%s\": {", typeProperty).eol();
367+
writer.append(" case \"%s\": {", typePropertyKey()).eol();
361368
writer.append(" type = stringJsonAdapter.fromJson(reader); break;").eol();
362369
writer.append(" }").eol();
363370
}
364371
for (FieldReader allField : allFields) {
365-
allField.writeFromJsonSwitch(writer, defaultConstructor, varName);
372+
allField.writeFromJsonSwitch(writer, defaultConstructor, varName, caseInsensitiveKeys);
366373
}
367374
writer.append(" default: {").eol();
375+
String unmappedFieldName = caseInsensitiveKeys ? "origFieldName" : "fieldName";
368376
if (unmappedField != null) {
369377
writer.append(" Object value = objectJsonAdapter.fromJson(reader);").eol();
370-
writer.append(" unmapped.put(fieldName, value);").eol();
378+
writer.append(" unmapped.put(%s, value);", unmappedFieldName).eol();
371379
} else {
372-
writer.append(" reader.unmappedField(fieldName);").eol();
380+
writer.append(" reader.unmappedField(%s);", unmappedFieldName).eol();
373381
writer.append(" reader.skipValue();").eol();
374382
}
375383
writer.append(" }").eol();
@@ -378,4 +386,8 @@ private void writeFromJsonSwitch(Append writer, boolean defaultConstructor, Stri
378386
writer.append(" reader.endObject();").eol();
379387
}
380388

389+
private String typePropertyKey() {
390+
return caseInsensitiveKeys ? typeProperty.toLowerCase() : typeProperty;
391+
}
392+
381393
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,17 +300,18 @@ private String pad(String value) {
300300
return sb.toString();
301301
}
302302

303-
void writeFromJsonSwitch(Append writer, boolean defaultConstructor, String varName) {
303+
void writeFromJsonSwitch(Append writer, boolean defaultConstructor, String varName, boolean caseInsensitiveKeys) {
304304
if (unmapped) {
305305
return;
306306
}
307307
if (aliases != null) {
308308
for (final String alias : aliases) {
309-
writer.append(" case \"%s\":", alias);
310-
writer.eol();
309+
String propertyKey = caseInsensitiveKeys ? alias.toLowerCase() : alias;
310+
writer.append(" case \"%s\":", propertyKey).eol();
311311
}
312312
}
313-
writer.append(" case \"%s\": {", propertyName).eol();
313+
String propertyKey = caseInsensitiveKeys ? propertyName.toLowerCase() : propertyName;
314+
writer.append(" case \"%s\": {", propertyKey).eol();
314315
if (!deserialize) {
315316
writer.append(" reader.skipValue();");
316317
} else if (defaultConstructor) {

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@ class NamingConventionReader {
1010
private static final String JSON_ANNOTATION = "io.avaje.jsonb.Json";
1111
private static final String NAMING_ATTRIBUTE = "naming()";
1212
private static final String TYPEPROPERTY_ATTRIBUTE = "typeProperty()";
13+
private static final String CASEINSENSITIVEKEYS_ATTRIBUTE = "caseInsensitiveKeys()";
1314

1415
private String typeProperty;
16+
private boolean caseInsensitiveKeys;
1517
private NamingConvention namingConvention;
1618

1719
NamingConventionReader(TypeElement element) {
1820
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
1921
if (JSON_ANNOTATION.equals(mirror.getAnnotationType().toString())) {
2022
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : mirror.getElementValues().entrySet()) {
21-
if (entry.getKey().toString().equals(NAMING_ATTRIBUTE)) {
23+
String key = entry.getKey().toString();
24+
if (key.equals(NAMING_ATTRIBUTE)) {
2225
namingConvention = NamingConvention.of(naming(entry.getValue().toString()));
23-
} else if (entry.getKey().toString().equals(TYPEPROPERTY_ATTRIBUTE)) {
26+
} else if (key.equals(TYPEPROPERTY_ATTRIBUTE)) {
2427
typeProperty = Util.trimQuotes(entry.getValue().toString());
28+
} else if (key.equals(CASEINSENSITIVEKEYS_ATTRIBUTE)) {
29+
caseInsensitiveKeys = Boolean.parseBoolean(entry.getValue().toString());
2530
}
2631
}
2732
}
@@ -43,4 +48,8 @@ NamingConvention get() {
4348
String typeProperty() {
4449
return typeProperty != null ? typeProperty : "@type";
4550
}
51+
52+
boolean isCaseInsensitiveKeys() {
53+
return caseInsensitiveKeys;
54+
}
4655
}

jsonb/src/main/java/io/avaje/jsonb/Json.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
*/
4747
String typeProperty() default "";
4848

49+
/**
50+
* When set to true on deserialization keys are matched insensitive to case.
51+
*/
52+
boolean caseInsensitiveKeys() default false;
53+
4954
/**
5055
* Specify types to generate JsonAdapters for.
5156
* <p>

0 commit comments

Comments
 (0)