1
1
package io .avaje .jsonb .generator ;
2
2
3
3
import static io .avaje .jsonb .generator .ProcessingContext .jdkVersion ;
4
+ import static io .avaje .jsonb .generator .ProcessingContext .previewEnabled ;
4
5
import static io .avaje .jsonb .generator .ProcessingContext .useEnhancedSwitch ;
6
+ import static java .util .stream .Collectors .toList ;
5
7
6
8
import java .lang .reflect .InvocationTargetException ;
7
- import java .util .HashSet ;
8
- import java .util .List ;
9
- import java .util .Objects ;
10
- import java .util .Set ;
11
- import java .util .TreeSet ;
9
+ import java .util .*;
12
10
13
11
import javax .lang .model .element .Element ;
14
12
import javax .lang .model .element .ElementKind ;
@@ -34,8 +32,10 @@ final class ClassReader implements BeanReader {
34
32
private final boolean isRecord ;
35
33
private final boolean usesTypeProperty ;
36
34
private final boolean useEnum ;
37
- private final boolean useInstanceofPattern = jdkVersion () >= 17 ;
38
- private final boolean nullSwitch = jdkVersion () >= 21 ;
35
+ private static final boolean useInstanceofPattern = jdkVersion () >= 17 ;
36
+ private static final boolean nullSwitch = jdkVersion () >= 21 || (jdkVersion () >= 17 && previewEnabled ());
37
+ private final Map <String , Integer > frequencyMap = new HashMap <>();
38
+ private final Map <String , Boolean > isCommonFieldMap = new HashMap <>();
39
39
40
40
ClassReader (TypeElement beanType ) {
41
41
this (beanType , null );
@@ -66,7 +66,6 @@ final class ClassReader implements BeanReader {
66
66
.map (FieldReader ::type )
67
67
.map (GenericType ::topType )
68
68
.map (ProcessingContext ::element )
69
- .filter (Objects ::nonNull )
70
69
.filter (e -> e .getKind () == ElementKind .ENUM )
71
70
.isPresent ();
72
71
}
@@ -200,6 +199,13 @@ public void writeConstructor(Append writer) {
200
199
final Set <String > uniqueTypes = new HashSet <>();
201
200
for (final FieldReader allField : allFields ) {
202
201
if (allField .include () && !allField .isRaw () && uniqueTypes .add (allField .adapterShortType ())) {
202
+ if (hasSubTypes ) {
203
+ final var isCommonDiffType =
204
+ allFields .stream ()
205
+ .filter (s -> s .fieldName ().equals (allField .fieldName ()))
206
+ .anyMatch (f -> !allField .adapterShortType ().equals (f .adapterShortType ()));
207
+ isCommonFieldMap .put (allField .fieldName (), isCommonDiffType );
208
+ }
203
209
allField .writeConstructor (writer );
204
210
}
205
211
}
@@ -208,8 +214,14 @@ public void writeConstructor(Append writer) {
208
214
writer .append ("\" " ).append (typeProperty ).append ("\" , " );
209
215
}
210
216
final StringBuilder builder = new StringBuilder ();
217
+
218
+ //set to prevent writing same key twice
219
+ final var seen = new HashSet <String >();
211
220
for (int i = 0 , size = allFields .size (); i < size ; i ++) {
212
221
final FieldReader fieldReader = allFields .get (i );
222
+ if (!seen .add (fieldReader .fieldName ())) {
223
+ continue ;
224
+ }
213
225
if (i > 0 ) {
214
226
builder .append (", " );
215
227
}
@@ -358,11 +370,16 @@ private void writeJsonBuildResult(Append writer, String varName) {
358
370
if (i > 0 ) {
359
371
writer .append (", " );
360
372
}
361
- writer .append (constructorParamName (params .get (i ).name ())); // assuming name matches field here?
373
+ final var name = params .get (i ).name ();
374
+ // append increasing numbers to constructor params sharing names with other subtypes
375
+ final var frequency = frequencyMap .compute (name , (k , v ) -> v == null ? 0 : v + 1 );
376
+ // assuming name matches field here?
377
+ writer .append (constructorParamName (name + (frequency == 0 ? "" : frequency .toString ())));
362
378
}
363
379
writer .append (");" ).eol ();
364
380
for (final FieldReader allField : allFields ) {
365
381
if (allField .includeFromJson ()) {
382
+ frequencyMap .compute (allField .fieldName (), (k , v ) -> v == null ? 0 : v + 1 );
366
383
allField .writeFromJsonSetter (writer , varName , "" );
367
384
}
368
385
}
@@ -387,10 +404,12 @@ private void writeFromJsonWithSubTypes(Append writer) {
387
404
writer .append (" case null -> " ).append ("throw new IllegalStateException(\" Missing Required %s property that determines deserialization type\" );" , typeProperty ).eol ();
388
405
}
389
406
}
407
+ // another frequency map to append numbers to the subtype constructor params
408
+ final Map <String , Integer > frequencyMap2 = new HashMap <>();
390
409
391
410
for (final TypeSubTypeMeta subTypeMeta : typeReader .subTypes ()) {
392
411
final var varName = Util .initLower (Util .shortName (subTypeMeta .type ()));
393
- subTypeMeta .writeFromJsonBuild (writer , typeVar , varName , this , useSwitch , useEnum );
412
+ subTypeMeta .writeFromJsonBuild (writer , typeVar , varName , this , useSwitch , useEnum , frequencyMap2 , isCommonFieldMap );
394
413
}
395
414
if (useSwitch ) {
396
415
writer .append (" default" ).appendSwitchCase ().eol ().append (" " );
@@ -431,8 +450,38 @@ private void writeFromJsonSwitch(Append writer, boolean defaultConstructor, Stri
431
450
writer .append (" type = stringJsonAdapter.fromJson(reader);" ).eol ();
432
451
writer .append (" break;" ).eol ();
433
452
}
453
+ // don't write same switch case twice
454
+ final var seen = new HashSet <>();
434
455
for (final FieldReader allField : allFields ) {
435
- allField .writeFromJsonSwitch (writer , defaultConstructor , varName , caseInsensitiveKeys );
456
+ final var name = allField .fieldName ();
457
+ if (!seen .add (name )) {
458
+ continue ;
459
+ }
460
+ if (hasSubTypes ) {
461
+ final var isCommonFieldDiffType = isCommonFieldMap .get (name );
462
+ if (isCommonFieldDiffType == null || !isCommonFieldDiffType ) {
463
+ allField .writeFromJsonSwitch (
464
+ writer ,
465
+ defaultConstructor ,
466
+ varName ,
467
+ caseInsensitiveKeys ,
468
+ allFields .stream ()
469
+ .filter (x -> x .fieldName ().equals (name ))
470
+ .flatMap (f -> f .getAliases ().stream ())
471
+ .collect (toList ()));
472
+ } else {
473
+ // if subclass shares a field name with another subclass
474
+ // write a special case statement
475
+ writeSubTypeCase (
476
+ name ,
477
+ writer ,
478
+ allFields .stream ().filter (x -> x .fieldName ().equals (name )).collect (toList ()),
479
+ defaultConstructor ,
480
+ varName );
481
+ }
482
+
483
+ } else
484
+ allField .writeFromJsonSwitch (writer , defaultConstructor , varName , caseInsensitiveKeys , List .of ());
436
485
}
437
486
writer .append (" default:" ).eol ();
438
487
final String unmappedFieldName = caseInsensitiveKeys ? "origFieldName" : "fieldName" ;
@@ -448,6 +497,50 @@ private void writeFromJsonSwitch(Append writer, boolean defaultConstructor, Stri
448
497
writer .append (" reader.endObject();" ).eol ();
449
498
}
450
499
500
+ private void writeSubTypeCase (String name , Append writer , List <FieldReader > commonFields , boolean defaultConstructor , String varName ) {
501
+ writer .append (" case \" %s\" :" , name ).eol ();
502
+ // get all possible aliases of this field from the subtypes
503
+ for (final String alias :
504
+ commonFields .stream ().map (FieldReader ::getAliases ).findFirst ().orElseGet (List ::of )) {
505
+ final String propertyKey = caseInsensitiveKeys ? alias .toLowerCase () : alias ;
506
+ writer .append (" case \" %s\" :" , propertyKey ).eol ();
507
+ }
508
+ var elseIf = false ;
509
+ // write the case statements with subtypeCheck
510
+ for (final FieldReader fieldReader : commonFields ) {
511
+ final var subtype = new ArrayList <>(fieldReader .getSubTypes ().values ()).get (0 );
512
+ final var setter = fieldReader .getSetter ();
513
+ final var adapterFieldName = fieldReader .getAdapterFieldName ();
514
+ final var fieldName = fieldReader .getFieldNameWithNum ();
515
+ if (useEnum ) {
516
+ writer .append (" %sif (%s.equals(%s)) {" , elseIf ? "else " : "" , subtype .name (), "type" ).eol ();
517
+ } else {
518
+ writer .append (" %sif (\" %s\" .equals(%s)) {" , elseIf ? "else " : "" , subtype .name (), "type" ).eol ();
519
+ }
520
+ elseIf = true ;
521
+ if (!fieldReader .isDeserialize ()) {
522
+ writer .append (" reader.skipValue();" );
523
+ } else if (defaultConstructor ) {
524
+ if (setter != null ) {
525
+ writer .append (" _$%s.%s(%s.fromJson(reader));" , varName , setter .getName (), adapterFieldName );
526
+ } else if (fieldReader .isPublicField ()) {
527
+ writer .append (" _$%s.%s = %s.fromJson(reader);" , varName , fieldName , adapterFieldName );
528
+ }
529
+ } else {
530
+ writer .append (" _val$%s = %s.fromJson(reader);" , fieldName , adapterFieldName );
531
+ if (!fieldReader .isConstructorParam ()) {
532
+ writer .eol ().append (" _set$%s = true;" , fieldName );
533
+ }
534
+ }
535
+ writer .eol ().append (" }" ).eol ();
536
+ }
537
+ writer
538
+ .append (" else {" ).eol ()
539
+ .append (" throw new IllegalStateException(\" Missing Required type3 property that determines deserialization type\" );" ).eol ()
540
+ .append (" }" ).eol ()
541
+ .append (" break;" ).eol ().eol ();
542
+ }
543
+
451
544
private String typePropertyKey () {
452
545
return caseInsensitiveKeys ? typeProperty .toLowerCase () : typeProperty ;
453
546
}
0 commit comments