@@ -569,12 +569,30 @@ else if (fieldType == Point.class) {
569569 case GEO -> fields .add (SearchField .of (field , indexAsGeoFieldFor (field , true , prefix , indexed .alias ())));
570570 case VECTOR -> fields .add (SearchField .of (field , indexAsVectorFieldFor (field , isDocument , prefix , indexed )));
571571 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
572588 for (java .lang .reflect .Field subfield : com .redis .om .spring .util .ObjectUtils .getDeclaredFieldsTransitively (
573- field . getType () )) {
589+ nestedType )) {
574590 String subfieldPrefix = (prefix == null || prefix .isBlank ()) ?
575591 field .getName () :
576592 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 ));
578596 }
579597 }
580598 default -> {
@@ -869,6 +887,80 @@ private List<SearchField> getNestedField(String fieldPrefix, java.lang.reflect.F
869887 return fieldList ;
870888 }
871889
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+
872964 private TagField getTagField (FieldName fieldName , String separator , boolean sortable ) {
873965 return getTagField (fieldName , separator , sortable , false , false );
874966 }
0 commit comments