@@ -569,12 +569,30 @@ else if (fieldType == Point.class) {
569
569
case GEO -> fields .add (SearchField .of (field , indexAsGeoFieldFor (field , true , prefix , indexed .alias ())));
570
570
case VECTOR -> fields .add (SearchField .of (field , indexAsVectorFieldFor (field , isDocument , prefix , indexed )));
571
571
case NESTED -> {
572
+ Class <?> nestedType = field .getType ();
573
+
574
+ // Handle List<Model> fields by extracting the element type
575
+ if (List .class .isAssignableFrom (nestedType ) || Set .class .isAssignableFrom (nestedType )) {
576
+ Optional <Class <?>> maybeCollectionType = getCollectionElementClass (field );
577
+ if (maybeCollectionType .isPresent ()) {
578
+ nestedType = maybeCollectionType .get ();
579
+ logger .info (String .format ("Processing nested array field %s with element type %s" , field .getName (),
580
+ nestedType .getSimpleName ()));
581
+ } else {
582
+ logger .warn (String .format ("Could not determine element type for nested field %s" , field .getName ()));
583
+ break ;
584
+ }
585
+ }
586
+
587
+ // Process all fields of the nested type automatically
572
588
for (java .lang .reflect .Field subfield : com .redis .om .spring .util .ObjectUtils .getDeclaredFieldsTransitively (
573
- field . getType () )) {
589
+ nestedType )) {
574
590
String subfieldPrefix = (prefix == null || prefix .isBlank ()) ?
575
591
field .getName () :
576
592
String .join ("." , prefix , field .getName ());
577
- fields .addAll (findIndexFields (subfield , subfieldPrefix , isDocument ));
593
+
594
+ // For nested fields, automatically create index fields even without explicit annotations
595
+ fields .addAll (createNestedIndexFields (field , subfield , subfieldPrefix , isDocument ));
578
596
}
579
597
}
580
598
default -> {
@@ -869,6 +887,80 @@ private List<SearchField> getNestedField(String fieldPrefix, java.lang.reflect.F
869
887
return fieldList ;
870
888
}
871
889
890
+ /**
891
+ * Creates index fields for nested array elements automatically.
892
+ * This method handles automatic indexing of all fields within nested objects
893
+ * when @Indexed(schemaFieldType = SchemaFieldType.NESTED) is used.
894
+ */
895
+ private List <SearchField > createNestedIndexFields (java .lang .reflect .Field arrayField ,
896
+ java .lang .reflect .Field nestedField , String prefix , boolean isDocument ) {
897
+ List <SearchField > fields = new ArrayList <>();
898
+
899
+ Class <?> nestedFieldType = ClassUtils .resolvePrimitiveIfNecessary (nestedField .getType ());
900
+
901
+ // For nested arrays, the path should be: $.arrayField[*].nestedField
902
+ // The prefix already contains the array field name, so we just need [*].nestedField
903
+ String fullFieldPath = isDocument ?
904
+ "$." + arrayField .getName () + "[*]." + nestedField .getName () :
905
+ arrayField .getName () + "[*]." + nestedField .getName ();
906
+
907
+ logger .info (String .format ("Creating automatic nested field index: %s -> %s" , arrayField .getName (), fullFieldPath ));
908
+
909
+ // Determine field type and create appropriate index field
910
+ if (CharSequence .class .isAssignableFrom (
911
+ nestedFieldType ) || nestedFieldType == Boolean .class || nestedFieldType == UUID .class || nestedFieldType == Ulid .class ) {
912
+
913
+ // Create TAG field for strings, booleans, UUIDs, and ULIDs
914
+ FieldName fieldName = FieldName .of (fullFieldPath );
915
+ String alias = QueryUtils .searchIndexFieldAliasFor (nestedField , prefix );
916
+ if (alias != null && !alias .isEmpty ()) {
917
+ fieldName = fieldName .as (alias );
918
+ }
919
+
920
+ fields .add (SearchField .of (arrayField , getTagField (fieldName , "|" , false )));
921
+
922
+ } else if (Number .class .isAssignableFrom (
923
+ nestedFieldType ) || nestedFieldType == LocalDateTime .class || nestedFieldType == LocalDate .class || nestedFieldType == Date .class || nestedFieldType == Instant .class || nestedFieldType == OffsetDateTime .class ) {
924
+
925
+ // Create NUMERIC field for numbers and dates
926
+ FieldName fieldName = FieldName .of (fullFieldPath );
927
+ String alias = QueryUtils .searchIndexFieldAliasFor (nestedField , prefix );
928
+ if (alias != null && !alias .isEmpty ()) {
929
+ fieldName = fieldName .as (alias );
930
+ }
931
+
932
+ fields .add (SearchField .of (arrayField , NumericField .of (fieldName )));
933
+
934
+ } else if (nestedFieldType == Point .class ) {
935
+
936
+ // Create GEO field for Point objects
937
+ FieldName fieldName = FieldName .of (fullFieldPath );
938
+ String alias = QueryUtils .searchIndexFieldAliasFor (nestedField , prefix );
939
+ if (alias != null && !alias .isEmpty ()) {
940
+ fieldName = fieldName .as (alias );
941
+ }
942
+
943
+ fields .add (SearchField .of (arrayField , GeoField .of (fieldName )));
944
+
945
+ } else if (nestedFieldType .isEnum ()) {
946
+
947
+ // Create TAG field for enums
948
+ FieldName fieldName = FieldName .of (fullFieldPath );
949
+ String alias = QueryUtils .searchIndexFieldAliasFor (nestedField , prefix );
950
+ if (alias != null && !alias .isEmpty ()) {
951
+ fieldName = fieldName .as (alias );
952
+ }
953
+
954
+ fields .add (SearchField .of (arrayField , getTagField (fieldName , "|" , false )));
955
+
956
+ } else {
957
+ logger .debug (String .format ("Skipping nested field %s of unsupported type %s" , nestedField .getName (),
958
+ nestedFieldType .getSimpleName ()));
959
+ }
960
+
961
+ return fields ;
962
+ }
963
+
872
964
private TagField getTagField (FieldName fieldName , String separator , boolean sortable ) {
873
965
return getTagField (fieldName , separator , sortable , false , false );
874
966
}
0 commit comments