11package io .avaje .jsonb .generator ;
22
33import static io .avaje .jsonb .generator .ProcessingContext .jdkVersion ;
4+ import static io .avaje .jsonb .generator .ProcessingContext .previewEnabled ;
45import static io .avaje .jsonb .generator .ProcessingContext .useEnhancedSwitch ;
6+ import static java .util .stream .Collectors .toList ;
57
68import 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 .*;
1210
1311import javax .lang .model .element .Element ;
1412import javax .lang .model .element .ElementKind ;
@@ -34,8 +32,10 @@ final class ClassReader implements BeanReader {
3432 private final boolean isRecord ;
3533 private final boolean usesTypeProperty ;
3634 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 <>();
3939
4040 ClassReader (TypeElement beanType ) {
4141 this (beanType , null );
@@ -66,7 +66,6 @@ final class ClassReader implements BeanReader {
6666 .map (FieldReader ::type )
6767 .map (GenericType ::topType )
6868 .map (ProcessingContext ::element )
69- .filter (Objects ::nonNull )
7069 .filter (e -> e .getKind () == ElementKind .ENUM )
7170 .isPresent ();
7271 }
@@ -200,6 +199,13 @@ public void writeConstructor(Append writer) {
200199 final Set <String > uniqueTypes = new HashSet <>();
201200 for (final FieldReader allField : allFields ) {
202201 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+ }
203209 allField .writeConstructor (writer );
204210 }
205211 }
@@ -208,8 +214,14 @@ public void writeConstructor(Append writer) {
208214 writer .append ("\" " ).append (typeProperty ).append ("\" , " );
209215 }
210216 final StringBuilder builder = new StringBuilder ();
217+
218+ //set to prevent writing same key twice
219+ final var seen = new HashSet <String >();
211220 for (int i = 0 , size = allFields .size (); i < size ; i ++) {
212221 final FieldReader fieldReader = allFields .get (i );
222+ if (!seen .add (fieldReader .fieldName ())) {
223+ continue ;
224+ }
213225 if (i > 0 ) {
214226 builder .append (", " );
215227 }
@@ -358,11 +370,16 @@ private void writeJsonBuildResult(Append writer, String varName) {
358370 if (i > 0 ) {
359371 writer .append (", " );
360372 }
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 ())));
362378 }
363379 writer .append (");" ).eol ();
364380 for (final FieldReader allField : allFields ) {
365381 if (allField .includeFromJson ()) {
382+ frequencyMap .compute (allField .fieldName (), (k , v ) -> v == null ? 0 : v + 1 );
366383 allField .writeFromJsonSetter (writer , varName , "" );
367384 }
368385 }
@@ -387,10 +404,12 @@ private void writeFromJsonWithSubTypes(Append writer) {
387404 writer .append (" case null -> " ).append ("throw new IllegalStateException(\" Missing Required %s property that determines deserialization type\" );" , typeProperty ).eol ();
388405 }
389406 }
407+ // another frequency map to append numbers to the subtype constructor params
408+ final Map <String , Integer > frequencyMap2 = new HashMap <>();
390409
391410 for (final TypeSubTypeMeta subTypeMeta : typeReader .subTypes ()) {
392411 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 );
394413 }
395414 if (useSwitch ) {
396415 writer .append (" default" ).appendSwitchCase ().eol ().append (" " );
@@ -431,8 +450,38 @@ private void writeFromJsonSwitch(Append writer, boolean defaultConstructor, Stri
431450 writer .append (" type = stringJsonAdapter.fromJson(reader);" ).eol ();
432451 writer .append (" break;" ).eol ();
433452 }
453+ // don't write same switch case twice
454+ final var seen = new HashSet <>();
434455 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 ());
436485 }
437486 writer .append (" default:" ).eol ();
438487 final String unmappedFieldName = caseInsensitiveKeys ? "origFieldName" : "fieldName" ;
@@ -448,6 +497,50 @@ private void writeFromJsonSwitch(Append writer, boolean defaultConstructor, Stri
448497 writer .append (" reader.endObject();" ).eol ();
449498 }
450499
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+
451544 private String typePropertyKey () {
452545 return caseInsensitiveKeys ? typeProperty .toLowerCase () : typeProperty ;
453546 }
0 commit comments