Skip to content

Commit 5dc89b5

Browse files
committed
fix: support @indexed(alias) on nested Map fields for uppercase JSON handling
- RediSearchIndexer now respects @indexed(alias) annotations on nested fields within Map value objects - RediSearchQuery properly handles aliases in MapContains queries and combined queries - Added comprehensive test coverage for uppercase JSON field mappings in Map complex objects - Fixes MapContains repository methods when JSON fields use different casing than Java fields This enables proper querying of Map fields containing complex objects where the JSON structure uses uppercase field names (e.g., from external systems) while maintaining standard Java naming conventions through @JsonProperty and @indexed(alias) annotations.
1 parent c451ca9 commit 5dc89b5

File tree

7 files changed

+635
-7
lines changed

7 files changed

+635
-7
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,11 @@ else if (Map.class.isAssignableFrom(fieldType) && isDocument) {
613613
String nestedJsonPath = (prefix == null || prefix.isBlank()) ?
614614
"$." + field.getName() + ".*." + subfield.getName() :
615615
"$." + prefix + "." + field.getName() + ".*." + subfield.getName();
616-
String nestedFieldAlias = field.getName() + "_" + subfield.getName();
616+
// Respect the alias annotation on the nested field
617+
String subfieldAlias = (subfieldIndexed.alias() != null && !subfieldIndexed.alias().isEmpty()) ?
618+
subfieldIndexed.alias() :
619+
subfield.getName();
620+
String nestedFieldAlias = field.getName() + "_" + subfieldAlias;
617621

618622
logger.info(String.format("Processing nested field %s in Map value type, path: %s, alias: %s",
619623
subfield.getName(), nestedJsonPath, nestedFieldAlias));

redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -409,12 +409,15 @@ private void processMapContainsQuery(String methodName) {
409409
List<Pair<String, QueryClause>> currentOrPart = new ArrayList<>();
410410

411411
for (String clause : clauses) {
412+
// Remove leading And/Or if present
413+
String cleanClause = clause.replaceFirst("^(And|Or)", "");
414+
412415
// Check if this clause contains MapContains pattern
413-
if (clause.contains("MapContains")) {
416+
if (cleanClause.contains("MapContains")) {
414417
// Extract the Map field and nested field
415418
Pattern pattern = Pattern.compile(
416419
"([A-Za-z]+)MapContains([A-Za-z]+)(GreaterThan|LessThan|After|Before|Between|NotEqual|In)?");
417-
Matcher matcher = pattern.matcher(clause);
420+
Matcher matcher = pattern.matcher(cleanClause);
418421

419422
if (matcher.find()) {
420423
String mapFieldName = matcher.group(1);
@@ -436,8 +439,15 @@ private void processMapContainsQuery(String methodName) {
436439
// Find the nested field in the value type
437440
Field nestedField = ReflectionUtils.findField(valueType, nestedFieldName);
438441
if (nestedField != null) {
439-
// Build the index field name: mapField_nestedField
440-
String indexFieldName = mapFieldName + "_" + nestedFieldName;
442+
// Build the index field name: mapField_nestedField (respecting alias if present)
443+
String actualNestedFieldName = nestedFieldName;
444+
if (nestedField.isAnnotationPresent(Indexed.class)) {
445+
Indexed indexed = nestedField.getAnnotation(Indexed.class);
446+
if (indexed.alias() != null && !indexed.alias().isEmpty()) {
447+
actualNestedFieldName = indexed.alias();
448+
}
449+
}
450+
String indexFieldName = mapFieldName + "_" + actualNestedFieldName;
441451

442452
// Determine the field type and part type
443453
Class<?> nestedFieldType = ClassUtils.resolvePrimitiveIfNecessary(nestedField.getType());
@@ -470,7 +480,7 @@ private void processMapContainsQuery(String methodName) {
470480
} else {
471481
// Handle regular field patterns - delegate to standard parsing
472482
// This is a simplified version - in production would need full parsing
473-
String fieldName = clause.replaceAll("(GreaterThan|LessThan|Between|NotEqual|In).*", "");
483+
String fieldName = cleanClause.replaceAll("(GreaterThan|LessThan|Between|NotEqual|In).*", "");
474484
fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
475485

476486
Field field = ReflectionUtils.findField(domainType, fieldName);
@@ -482,11 +492,20 @@ private void processMapContainsQuery(String methodName) {
482492
partType = Part.Type.LESS_THAN;
483493
}
484494

495+
// Check for @Indexed alias on regular fields
496+
String actualFieldName = fieldName;
497+
if (field.isAnnotationPresent(Indexed.class)) {
498+
Indexed indexed = field.getAnnotation(Indexed.class);
499+
if (indexed.alias() != null && !indexed.alias().isEmpty()) {
500+
actualFieldName = indexed.alias();
501+
}
502+
}
503+
485504
Class<?> fieldType = ClassUtils.resolvePrimitiveIfNecessary(field.getType());
486505
FieldType redisFieldType = getRedisFieldType(fieldType);
487506
if (redisFieldType != null) {
488507
QueryClause queryClause = QueryClause.get(redisFieldType, partType);
489-
currentOrPart.add(Pair.of(fieldName, queryClause));
508+
currentOrPart.add(Pair.of(actualFieldName, queryClause));
490509
}
491510
}
492511
}

0 commit comments

Comments
 (0)