diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java b/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java index b5554f19..4355bcce 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java @@ -562,10 +562,16 @@ else if (Map.class.isAssignableFrom(fieldType) && isDocument) { if (maybeValueType.isPresent()) { Class valueType = maybeValueType.get(); logger.info(String.format("Map field %s has value type: %s", field.getName(), valueType)); + + // Use the Map field's alias if specified, otherwise use the field name + String mapFieldNameForIndex = (indexed.alias() != null && !indexed.alias().isEmpty()) ? + indexed.alias() : + field.getName(); + String mapJsonPath = (prefix == null || prefix.isBlank()) ? "$." + field.getName() + ".*" : "$." + prefix + "." + field.getName() + ".*"; - String mapFieldAlias = field.getName() + "_values"; + String mapFieldAlias = mapFieldNameForIndex + "_values"; // Support all value types that we support for regular fields if (CharSequence.class.isAssignableFrom( @@ -610,14 +616,17 @@ else if (Map.class.isAssignableFrom(fieldType) && isDocument) { for (java.lang.reflect.Field subfield : getDeclaredFieldsTransitively(valueType)) { if (subfield.isAnnotationPresent(Indexed.class)) { Indexed subfieldIndexed = subfield.getAnnotation(Indexed.class); + // Get the actual JSON field name (check for @JsonProperty or @SerializedName) + String jsonFieldName = getJsonFieldName(subfield); String nestedJsonPath = (prefix == null || prefix.isBlank()) ? - "$." + field.getName() + ".*." + subfield.getName() : - "$." + prefix + "." + field.getName() + ".*." + subfield.getName(); + "$." + field.getName() + ".*." + jsonFieldName : + "$." + prefix + "." + field.getName() + ".*." + jsonFieldName; // Respect the alias annotation on the nested field String subfieldAlias = (subfieldIndexed.alias() != null && !subfieldIndexed.alias().isEmpty()) ? subfieldIndexed.alias() : subfield.getName(); - String nestedFieldAlias = field.getName() + "_" + subfieldAlias; + // Use the Map field's alias (if present) for the nested field alias prefix + String nestedFieldAlias = mapFieldNameForIndex + "_" + subfieldAlias; logger.info(String.format("Processing nested field %s in Map value type, path: %s, alias: %s", subfield.getName(), nestedJsonPath, nestedFieldAlias)); @@ -1318,6 +1327,29 @@ private String getFieldPrefix(String prefix, boolean isDocument) { return isDocument ? "$." + chain : chain; } + private String getJsonFieldName(java.lang.reflect.Field field) { + // Check for @JsonProperty annotation first + if (field.isAnnotationPresent(com.fasterxml.jackson.annotation.JsonProperty.class)) { + com.fasterxml.jackson.annotation.JsonProperty jsonProperty = field.getAnnotation( + com.fasterxml.jackson.annotation.JsonProperty.class); + if (jsonProperty.value() != null && !jsonProperty.value().isEmpty()) { + return jsonProperty.value(); + } + } + + // Check for @SerializedName annotation (Gson) + if (field.isAnnotationPresent(com.google.gson.annotations.SerializedName.class)) { + com.google.gson.annotations.SerializedName serializedName = field.getAnnotation( + com.google.gson.annotations.SerializedName.class); + if (serializedName.value() != null && !serializedName.value().isEmpty()) { + return serializedName.value(); + } + } + + // Default to field name + return field.getName(); + } + private void registerAlias(Class cl, String fieldName, String alias) { entityClassFieldToAlias.put(Tuples.of(cl, fieldName), alias); } diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/MetamodelGenerator.java b/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/MetamodelGenerator.java index b5e734b1..080032a5 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/MetamodelGenerator.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/MetamodelGenerator.java @@ -513,6 +513,7 @@ else if (Map.class.isAssignableFrom(targetCls)) { Element subfieldElement = enclosedElement; if (subfieldElement.getAnnotation(com.redis.om.spring.annotations.Indexed.class) != null) { String subfieldName = subfieldElement.getSimpleName().toString(); + String jsonFieldName = getJsonFieldName(subfieldElement); String nestedFieldName = field.getSimpleName().toString().toUpperCase().replace("_", "") + "_" + subfieldName.toUpperCase().replace("_", ""); @@ -548,7 +549,7 @@ else if (Map.class.isAssignableFrom(targetCls)) { String uniqueFieldName = chainedFieldName + "_" + subfieldName; Triple nestedField = generateMapNestedFieldMetamodel( entity, chain, uniqueFieldName, nestedFieldName, nestedInterceptor, subfieldTypeName, field - .getSimpleName().toString(), subfieldName); + .getSimpleName().toString(), subfieldName, jsonFieldName); fieldMetamodelSpec.add(nestedField); messager.printMessage(Diagnostic.Kind.NOTE, @@ -1048,7 +1049,7 @@ private Triple generateFieldMetamode private Triple generateMapNestedFieldMetamodel(TypeName entity, List chain, String chainFieldName, String nestedFieldName, Class interceptorClass, - String subfieldTypeName, String mapFieldName, String subfieldName) { + String subfieldTypeName, String mapFieldName, String subfieldName, String jsonFieldName) { String fieldAccessor = ObjectUtils.staticField(nestedFieldName); FieldSpec objectField = FieldSpec.builder(Field.class, chainFieldName).addModifiers(Modifier.PUBLIC, @@ -1070,9 +1071,10 @@ private Triple generateMapNestedFiel FieldSpec aField = FieldSpec.builder(interceptor, fieldAccessor).addModifiers(Modifier.PUBLIC, Modifier.STATIC) .build(); - // Create the JSONPath for nested Map field: $.mapField.*.subfieldName - String alias = mapFieldName + "_" + subfieldName; - String jsonPath = "$." + mapFieldName + ".*." + subfieldName; + // Create the JSONPath for nested Map field: $.mapField.*.jsonFieldName + // Use JSON field name for both alias and path to match what the indexer creates + String alias = mapFieldName + "_" + jsonFieldName; + String jsonPath = "$." + mapFieldName + ".*." + jsonFieldName; CodeBlock aFieldInit = CodeBlock.builder().addStatement( "$L = new $T(new $T(\"$L\", \"$L\", $T.class, $T.class), true)", fieldAccessor, interceptor, @@ -1093,6 +1095,45 @@ private Pair generateUnboundMetamodelField(TypeName entity return Tuples.of(aField, aFieldInit); } + /** + * Get the JSON field name for a field element, checking for @JsonProperty and @SerializedName annotations. + * Falls back to the Java field name if no JSON annotation is found. + */ + private String getJsonFieldName(Element fieldElement) { + // Check for @JsonProperty annotation first + for (AnnotationMirror mirror : fieldElement.getAnnotationMirrors()) { + String annotationType = mirror.getAnnotationType().toString(); + if ("com.fasterxml.jackson.annotation.JsonProperty".equals(annotationType)) { + for (Map.Entry entry : mirror.getElementValues() + .entrySet()) { + if ("value".equals(entry.getKey().getSimpleName().toString())) { + String value = entry.getValue().getValue().toString(); + if (value != null && !value.isEmpty() && !value.equals("\"\"")) { + // Remove quotes from the annotation value + return value.replaceAll("^\"|\"$", ""); + } + } + } + } + // Check for @SerializedName annotation (Gson) + else if ("com.google.gson.annotations.SerializedName".equals(annotationType)) { + for (Map.Entry entry : mirror.getElementValues() + .entrySet()) { + if ("value".equals(entry.getKey().getSimpleName().toString())) { + String value = entry.getValue().getValue().toString(); + if (value != null && !value.isEmpty() && !value.equals("\"\"")) { + // Remove quotes from the annotation value + return value.replaceAll("^\"|\"$", ""); + } + } + } + } + } + + // Default to field name + return fieldElement.getSimpleName().toString(); + } + private Pair generateThisMetamodelField(TypeName entity) { String name = "_THIS"; String alias = "__this"; diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java index 835ba3fd..adf9c63d 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java @@ -444,6 +444,14 @@ private void processMapContainsQuery(String methodName) { logger.debug(String.format("Looking for Map field '%s' (or '%s') in %s: %s", mapFieldName, originalMapFieldName, domainType.getSimpleName(), mapField != null ? "FOUND" : "NOT FOUND")); if (mapField != null && Map.class.isAssignableFrom(mapField.getType())) { + // Check if the Map field has an @Indexed alias + String mapFieldNameForIndex = mapFieldName; + if (mapField.isAnnotationPresent(Indexed.class)) { + Indexed mapIndexed = mapField.getAnnotation(Indexed.class); + if (mapIndexed.alias() != null && !mapIndexed.alias().isEmpty()) { + mapFieldNameForIndex = mapIndexed.alias(); + } + } // Get the Map's value type Optional> maybeValueType = ObjectUtils.getMapValueClass(mapField); if (maybeValueType.isPresent()) { @@ -460,7 +468,7 @@ private void processMapContainsQuery(String methodName) { actualNestedFieldName = indexed.alias(); } } - String indexFieldName = mapFieldName + "_" + actualNestedFieldName; + String indexFieldName = mapFieldNameForIndex + "_" + actualNestedFieldName; // Determine the field type and part type Class nestedFieldType = ClassUtils.resolvePrimitiveIfNecessary(nestedField.getType()); diff --git a/tests/src/test/java/com/redis/om/spring/annotations/document/MapComplexObjectUpperCaseTest.java b/tests/src/test/java/com/redis/om/spring/annotations/document/MapComplexObjectUpperCaseTest.java index 45852df5..b6836802 100644 --- a/tests/src/test/java/com/redis/om/spring/annotations/document/MapComplexObjectUpperCaseTest.java +++ b/tests/src/test/java/com/redis/om/spring/annotations/document/MapComplexObjectUpperCaseTest.java @@ -98,7 +98,6 @@ void loadTestData() throws IOException { position.setManager("DEFAULT_MANAGER"); position.setDescription("DEFAULT_DESCRIPTION"); position.setPrice(new BigDecimal("100.00")); - position.setAsOfDate(LocalDate.now()); positions.put(posEntry.getKey(), position); } @@ -115,9 +114,9 @@ void loadTestData() throws IOException { @Test void testFindByManager() { // This should work because manager field uses @Indexed(alias = "MANAGER") - List accounts = repository.findByManager("Emma Jones"); + List accounts = repository.findByManager("Manager Gamma"); assertThat(accounts).isNotEmpty(); - assertThat(accounts.get(0).getManager()).isEqualTo("Emma Jones"); + assertThat(accounts.get(0).getManager()).isEqualTo("Manager Gamma"); } @Test diff --git a/tests/src/test/java/com/redis/om/spring/annotations/document/MapContainsNonStandardJsonFieldsTest.java b/tests/src/test/java/com/redis/om/spring/annotations/document/MapContainsNonStandardJsonFieldsTest.java new file mode 100644 index 00000000..f59cab12 --- /dev/null +++ b/tests/src/test/java/com/redis/om/spring/annotations/document/MapContainsNonStandardJsonFieldsTest.java @@ -0,0 +1,102 @@ +package com.redis.om.spring.annotations.document; + +import com.google.gson.Gson; +import com.redis.om.spring.AbstractBaseDocumentTest; +import com.redis.om.spring.fixtures.document.model.AccountUC; +import com.redis.om.spring.fixtures.document.repository.AccountUCRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class MapContainsNonStandardJsonFieldsTest extends AbstractBaseDocumentTest { + + @Autowired + AccountUCRepository repository; + + @BeforeEach + void loadTestData() throws IOException, InterruptedException { + // Clear any existing data + repository.deleteAll(); + + // Wait for index to be recreated + Thread.sleep(2000); + + // Load test data from JSON file with non-standard (uppercase) field names + Gson gson = new Gson(); + ClassPathResource resource = new ClassPathResource("data/uppercase-json-fields-subset.json"); + + try (InputStreamReader reader = new InputStreamReader(resource.getInputStream())) { + AccountUC[] accounts = gson.fromJson(reader, AccountUC[].class); + + // Save all accounts to Redis + List savedAccounts = repository.saveAll(Arrays.asList(accounts)); + + // Verify data was saved + System.out.println("Saved " + savedAccounts.size() + " accounts to Redis"); + for (AccountUC account : savedAccounts) { + System.out.println("Account " + account.getAccountId() + " has " + account.getPositions().size() + " positions"); + } + } + } + + @Test + void testMapContainsWithUppercaseJsonFields() { + // Query for accounts with TSLA positions + List accounts = repository.findByPositionsMapContainsCusip("TSLA"); + + // Verify we found the expected accounts (5 out of 7 have TSLA) + assertThat(accounts).hasSize(5); + + // Verify the account IDs match expected (5 accounts have TSLA positions) + List expectedIds = List.of("ACC-001", "ACC-002", "ACC-003", "ACC-004", "ACC-005"); + List actualIds = accounts.stream() + .map(AccountUC::getAccountId) + .sorted() + .toList(); + assertThat(actualIds).containsExactlyInAnyOrderElementsOf(expectedIds); + + // Verify each account actually has TSLA positions + for (AccountUC account : accounts) { + boolean hasTSLA = account.getPositions().values().stream() + .anyMatch(position -> "TSLA".equals(position.getCusip())); + assertThat(hasTSLA) + .as("Account %s should have TSLA position", account.getAccountId()) + .isTrue(); + } + } + + @Test + void testMapContainsWithNonMatchingCusip() { + // Query for accounts with a CUSIP that doesn't exist in our subset + List accounts = repository.findByPositionsMapContainsCusip("GOOGL"); + + // Should return empty list + assertThat(accounts).isEmpty(); + } + + @Test + void testMapContainsWithOtherCusips() { + // Query for accounts with AAPL positions + List accounts = repository.findByPositionsMapContainsCusip("AAPL"); + + // 6 out of 7 accounts have AAPL + assertThat(accounts).hasSize(6); + + // Verify each account actually has AAPL positions + for (AccountUC account : accounts) { + boolean hasAAPL = account.getPositions().values().stream() + .anyMatch(position -> "AAPL".equals(position.getCusip())); + assertThat(hasAAPL) + .as("Account %s should have AAPL position", account.getAccountId()) + .isTrue(); + } + } +} \ No newline at end of file diff --git a/tests/src/test/java/com/redis/om/spring/fixtures/document/model/AccountUC.java b/tests/src/test/java/com/redis/om/spring/fixtures/document/model/AccountUC.java index d880e7ce..c901f6ed 100644 --- a/tests/src/test/java/com/redis/om/spring/fixtures/document/model/AccountUC.java +++ b/tests/src/test/java/com/redis/om/spring/fixtures/document/model/AccountUC.java @@ -1,9 +1,9 @@ package com.redis.om.spring.fixtures.document.model; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; import com.redis.om.spring.annotations.Document; import com.redis.om.spring.annotations.Indexed; -import com.redis.om.spring.annotations.IndexingOptions; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; @@ -19,45 +19,54 @@ */ @Data @NoArgsConstructor -@Document -@IndexingOptions(indexName = "AccountUCIdx") +@Document(indexName = "idx:om:accounts", prefixes = {"accounts:ACCOUNTID:"}) public class AccountUC { @Id @JsonProperty("ACCOUNTID") + @SerializedName("ACCOUNTID") private String accountId; @Indexed(alias = "ACC_NAME") @JsonProperty("ACC_NAME") + @SerializedName("ACC_NAME") private String accountName; @Indexed(alias = "MANAGER") @JsonProperty("MANAGER") + @SerializedName("MANAGER") private String manager; @Indexed(alias = "ACC_VALUE") @JsonProperty("ACC_VALUE") + @SerializedName("ACC_VALUE") private BigDecimal accountValue; // Additional fields from VOYA data @Indexed @JsonProperty("COMMISSION_RATE") + @SerializedName("COMMISSION_RATE") private Integer commissionRate; @Indexed @JsonProperty("CASH_BALANCE") + @SerializedName("CASH_BALANCE") private BigDecimal cashBalance; @JsonProperty("DAY_CHANGE") + @SerializedName("DAY_CHANGE") private BigDecimal dayChange; @JsonProperty("UNREALIZED_GAIN_LOSS") + @SerializedName("UNREALIZED_GAIN_LOSS") private BigDecimal unrealizedGainLoss; @JsonProperty("MANAGER_FNAME") + @SerializedName("MANAGER_FNAME") private String managerFirstName; @JsonProperty("MANAGER_LNAME") + @SerializedName("MANAGER_LNAME") private String managerLastName; // Map with complex object values containing indexed fields @@ -65,6 +74,7 @@ public class AccountUC { // WITHOUT the alias, the repository method findByPositionsMapContainsCusip SHOULD FAIL @Indexed @JsonProperty("Positions") + @SerializedName("Positions") private Map Positions = new HashMap<>(); // Alternative for testing: lowercase field name with uppercase JSON property diff --git a/tests/src/test/java/com/redis/om/spring/fixtures/document/model/PositionUC.java b/tests/src/test/java/com/redis/om/spring/fixtures/document/model/PositionUC.java index 935b428e..1a81c646 100644 --- a/tests/src/test/java/com/redis/om/spring/fixtures/document/model/PositionUC.java +++ b/tests/src/test/java/com/redis/om/spring/fixtures/document/model/PositionUC.java @@ -1,12 +1,12 @@ package com.redis.om.spring.fixtures.document.model; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; import com.redis.om.spring.annotations.Indexed; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; -import java.time.LocalDate; /** * Position model for testing Map complex object queries with uppercase JSON fields. @@ -19,18 +19,22 @@ public class PositionUC { @Indexed(alias = "POSITIONID") @JsonProperty("POSITIONID") + @SerializedName("POSITIONID") private String positionId; @Indexed(alias = "CUSIP") @JsonProperty("CUSIP") + @SerializedName("CUSIP") private String cusip; @Indexed(alias = "QUANTITY") @JsonProperty("QUANTITY") + @SerializedName("QUANTITY") private Integer quantity; // Additional fields that might be in the Position object @JsonProperty("ACCOUNTID") + @SerializedName("ACCOUNTID") private String accountId; // Optional fields for more complete testing @@ -46,7 +50,4 @@ public class PositionUC { @JsonProperty("PRICE") private BigDecimal price; - @Indexed - @JsonProperty("AS_OF_DATE") - private LocalDate asOfDate; } \ No newline at end of file diff --git a/tests/src/test/resources/data/uppercase-json-fields-subset.json b/tests/src/test/resources/data/uppercase-json-fields-subset.json new file mode 100644 index 00000000..94ee7a0d --- /dev/null +++ b/tests/src/test/resources/data/uppercase-json-fields-subset.json @@ -0,0 +1,256 @@ +[ + { + "ACCOUNTID": "ACC-001", + "ACC_NAME": "Test Company A", + "MANAGER": "Manager One", + "COMMISSION_RATE": 2, + "CASH_BALANCE": 389094, + "ACC_VALUE": 6916995, + "DAY_CHANGE": 103277, + "UNREALIZED_GAIN_LOSS": 4764614, + "MANAGER_FNAME": "Manager", + "MANAGER_LNAME": "One", + "Positions": { + "P-11368": { + "POSITIONID": "P-11368", + "ACCOUNTID": "ACC-001", + "CUSIP": "JNJ", + "QUANTITY": 69075 + }, + "P-11367": { + "POSITIONID": "P-11367", + "ACCOUNTID": "ACC-001", + "CUSIP": "TSLA", + "QUANTITY": 29332 + }, + "P-11366": { + "POSITIONID": "P-11366", + "ACCOUNTID": "ACC-001", + "CUSIP": "JNJ", + "QUANTITY": 35960 + }, + "P-11369": { + "POSITIONID": "P-11369", + "ACCOUNTID": "ACC-001", + "CUSIP": "AAPL", + "QUANTITY": 9322 + } + } + }, + { + "ACCOUNTID": "ACC-002", + "ACC_NAME": "Test Company B", + "MANAGER": "Manager Two", + "COMMISSION_RATE": 3, + "CASH_BALANCE": 79285, + "ACC_VALUE": 9121820, + "DAY_CHANGE": -4708, + "UNREALIZED_GAIN_LOSS": 1719106, + "MANAGER_FNAME": "Manager", + "MANAGER_LNAME": "Two", + "Positions": { + "P-4866": { + "POSITIONID": "P-4866", + "ACCOUNTID": "ACC-002", + "CUSIP": "BRK", + "QUANTITY": 63377 + }, + "P-4865": { + "POSITIONID": "P-4865", + "ACCOUNTID": "ACC-002", + "CUSIP": "AAPL", + "QUANTITY": 31767 + }, + "P-4867": { + "POSITIONID": "P-4867", + "ACCOUNTID": "ACC-002", + "CUSIP": "UNH", + "QUANTITY": 2991 + }, + "P-4864": { + "POSITIONID": "P-4864", + "ACCOUNTID": "ACC-002", + "CUSIP": "META", + "QUANTITY": 66482 + }, + "P-4868": { + "POSITIONID": "P-4868", + "ACCOUNTID": "ACC-002", + "CUSIP": "TSLA", + "QUANTITY": 67824 + } + } + }, + { + "ACCOUNTID": "ACC-003", + "ACC_NAME": "Test Company C", + "MANAGER": "Manager Three", + "COMMISSION_RATE": 2, + "CASH_BALANCE": 86322, + "ACC_VALUE": 1196061, + "DAY_CHANGE": 104617, + "UNREALIZED_GAIN_LOSS": -697716, + "MANAGER_FNAME": "Manager", + "MANAGER_LNAME": "Three", + "Positions": { + "P-33480": { + "POSITIONID": "P-33480", + "ACCOUNTID": "ACC-003", + "CUSIP": "TSLA", + "QUANTITY": 45029 + }, + "P-33479": { + "POSITIONID": "P-33479", + "ACCOUNTID": "ACC-003", + "CUSIP": "CVS", + "QUANTITY": 29507 + }, + "P-33481": { + "POSITIONID": "P-33481", + "ACCOUNTID": "ACC-003", + "CUSIP": "META", + "QUANTITY": 49114 + } + } + }, + { + "ACCOUNTID": "ACC-004", + "ACC_NAME": "Test Company D", + "MANAGER": "Manager Four", + "COMMISSION_RATE": 3, + "CASH_BALANCE": 87388, + "ACC_VALUE": 4369229, + "DAY_CHANGE": 21873, + "UNREALIZED_GAIN_LOSS": 1761910, + "MANAGER_FNAME": "Manager", + "MANAGER_LNAME": "Four", + "Positions": { + "P-22011": { + "POSITIONID": "P-22011", + "ACCOUNTID": "ACC-004", + "CUSIP": "META", + "QUANTITY": 87761 + }, + "P-22010": { + "POSITIONID": "P-22010", + "ACCOUNTID": "ACC-004", + "CUSIP": "AAPL", + "QUANTITY": 33857 + }, + "P-22012": { + "POSITIONID": "P-22012", + "ACCOUNTID": "ACC-004", + "CUSIP": "TSLA", + "QUANTITY": 14812 + } + } + }, + { + "ACCOUNTID": "ACC-005", + "ACC_NAME": "Test Company E", + "MANAGER": "Manager Five", + "COMMISSION_RATE": 4, + "CASH_BALANCE": 442661, + "ACC_VALUE": 9866591, + "DAY_CHANGE": 126225, + "UNREALIZED_GAIN_LOSS": 480071, + "MANAGER_FNAME": "Manager", + "MANAGER_LNAME": "Five", + "Positions": { + "P-15917": { + "POSITIONID": "P-15917", + "ACCOUNTID": "ACC-005", + "CUSIP": "META", + "QUANTITY": 70909 + }, + "P-15921": { + "POSITIONID": "P-15921", + "ACCOUNTID": "ACC-005", + "CUSIP": "JNJ", + "QUANTITY": 4490 + }, + "P-15920": { + "POSITIONID": "P-15920", + "ACCOUNTID": "ACC-005", + "CUSIP": "AMZN", + "QUANTITY": 34975 + }, + "P-15918": { + "POSITIONID": "P-15918", + "ACCOUNTID": "ACC-005", + "CUSIP": "AAPL", + "QUANTITY": 9538 + }, + "P-15919": { + "POSITIONID": "P-15919", + "ACCOUNTID": "ACC-005", + "CUSIP": "TSLA", + "QUANTITY": 33318 + } + } + }, + { + "ACCOUNTID": "ACC-006", + "ACC_NAME": "Test Company F", + "MANAGER": "Manager Six", + "COMMISSION_RATE": 3, + "CASH_BALANCE": 372838, + "ACC_VALUE": 6129882, + "DAY_CHANGE": 143574, + "UNREALIZED_GAIN_LOSS": 738082, + "MANAGER_FNAME": "Manager", + "MANAGER_LNAME": "Six", + "Positions": { + "P-8846": { + "POSITIONID": "P-8846", + "ACCOUNTID": "ACC-006", + "CUSIP": "AAPL", + "QUANTITY": 18915 + }, + "P-8844": { + "POSITIONID": "P-8844", + "ACCOUNTID": "ACC-006", + "CUSIP": "AAPL", + "QUANTITY": 19720 + }, + "P-8845": { + "POSITIONID": "P-8845", + "ACCOUNTID": "ACC-006", + "CUSIP": "AAPL", + "QUANTITY": 34768 + } + } + }, + { + "ACCOUNTID": "ACC-007", + "ACC_NAME": "Test Company G", + "MANAGER": "Manager Seven", + "COMMISSION_RATE": 2, + "CASH_BALANCE": 423005, + "ACC_VALUE": 8573393, + "DAY_CHANGE": -165548, + "UNREALIZED_GAIN_LOSS": 4064817, + "MANAGER_FNAME": "Manager", + "MANAGER_LNAME": "Seven", + "Positions": { + "P-8604": { + "POSITIONID": "P-8604", + "ACCOUNTID": "ACC-007", + "CUSIP": "AAPL", + "QUANTITY": 28547 + }, + "P-8605": { + "POSITIONID": "P-8605", + "ACCOUNTID": "ACC-007", + "CUSIP": "CVS", + "QUANTITY": 71734 + }, + "P-8606": { + "POSITIONID": "P-8606", + "ACCOUNTID": "ACC-007", + "CUSIP": "AAPL", + "QUANTITY": 18220 + } + } + } +] \ No newline at end of file diff --git a/tests/src/test/resources/data/uppercase.json b/tests/src/test/resources/data/uppercase.json index 5b688192..0e9f6736 100644 --- a/tests/src/test/resources/data/uppercase.json +++ b/tests/src/test/resources/data/uppercase.json @@ -1,5 +1,5 @@ [ - {"key":"accounts:ACCOUNTID:ACC-3342","timestamp":"2025-09-10T16:16:10.893675Z","event":"scan","type":"json","value":"{\"ACCOUNTID\":\"ACC-3342\",\"ACC_NAME\":\"Renaissance Technologies\",\"MANAGER\":\"Carly Smith\",\"COMMISSION_RATE\":4,\"CASH_BALANCE\":197315,\"ACC_VALUE\":7543708,\"DAY_CHANGE\":-154894,\"UNREALIZED_GAIN_LOSS\":-977099,\"MANAGER_FNAME\":\"Carly\",\"MANAGER_LNAME\":\"Smith\",\"Positions\":{\"P-13361\":{\"POSITIONID\":\"P-13361\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"TSLA\",\"QUANTITY\":63137},\"P-13360\":{\"POSITIONID\":\"P-13360\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"JNJ\",\"QUANTITY\":26676},\"P-13364\":{\"POSITIONID\":\"P-13364\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"AAPL\",\"QUANTITY\":7262},\"P-13363\":{\"POSITIONID\":\"P-13363\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"CVS\",\"QUANTITY\":82975},\"P-13362\":{\"POSITIONID\":\"P-13362\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"AAPL\",\"QUANTITY\":83382}}}"}, - {"key":"accounts:ACCOUNTID:ACC-4167","timestamp":"2025-09-10T16:16:10.893836Z","event":"scan","type":"json","value":"{\"ACCOUNTID\":\"ACC-4167\",\"ACC_NAME\":\"Lazard Asset Management\",\"MANAGER\":\"Mason Wilson\",\"COMMISSION_RATE\":3,\"CASH_BALANCE\":263920,\"ACC_VALUE\":2314973,\"DAY_CHANGE\":151377,\"UNREALIZED_GAIN_LOSS\":311809,\"MANAGER_FNAME\":\"Mason\",\"MANAGER_LNAME\":\"Wilson\",\"Positions\":{\"P-16621\":{\"POSITIONID\":\"P-16621\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"AAPL\",\"QUANTITY\":27012},\"P-16620\":{\"POSITIONID\":\"P-16620\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"TSLA\",\"QUANTITY\":6026},\"P-16624\":{\"POSITIONID\":\"P-16624\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"CVS\",\"QUANTITY\":46269},\"P-16622\":{\"POSITIONID\":\"P-16622\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"AAPL\",\"QUANTITY\":14136},\"P-16623\":{\"POSITIONID\":\"P-16623\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"META\",\"QUANTITY\":54401}}}"}, - {"key":"accounts:ACCOUNTID:ACC-3230","timestamp":"2025-09-10T16:16:10.893844Z","event":"scan","type":"json","value":"{\"ACCOUNTID\":\"ACC-3230\",\"ACC_NAME\":\"Ares Management\",\"MANAGER\":\"Emma Jones\",\"COMMISSION_RATE\":4,\"CASH_BALANCE\":334454,\"ACC_VALUE\":9357169,\"DAY_CHANGE\":-81901,\"UNREALIZED_GAIN_LOSS\":-140338,\"MANAGER_FNAME\":\"Emma\",\"MANAGER_LNAME\":\"Jones\",\"Positions\":{\"P-12897\":{\"POSITIONID\":\"P-12897\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"TSLA\",\"QUANTITY\":27382},\"P-12900\":{\"POSITIONID\":\"P-12900\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"TSLA\",\"QUANTITY\":4083},\"P-12899\":{\"POSITIONID\":\"P-12899\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"TSLA\",\"QUANTITY\":79731},\"P-12898\":{\"POSITIONID\":\"P-12898\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"AAPL\",\"QUANTITY\":38186}}}"} + {"key":"accounts:ACCOUNTID:ACC-3342","timestamp":"2025-09-10T16:16:10.893675Z","event":"scan","type":"json","value":"{\"ACCOUNTID\":\"ACC-3342\",\"ACC_NAME\":\"Test Corporation Alpha\",\"MANAGER\":\"Manager Alpha\",\"COMMISSION_RATE\":4,\"CASH_BALANCE\":197315,\"ACC_VALUE\":7543708,\"DAY_CHANGE\":-154894,\"UNREALIZED_GAIN_LOSS\":-977099,\"MANAGER_FNAME\":\"Manager\",\"MANAGER_LNAME\":\"Alpha\",\"Positions\":{\"P-13361\":{\"POSITIONID\":\"P-13361\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"TSLA\",\"QUANTITY\":63137},\"P-13360\":{\"POSITIONID\":\"P-13360\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"JNJ\",\"QUANTITY\":26676},\"P-13364\":{\"POSITIONID\":\"P-13364\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"AAPL\",\"QUANTITY\":7262},\"P-13363\":{\"POSITIONID\":\"P-13363\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"CVS\",\"QUANTITY\":82975},\"P-13362\":{\"POSITIONID\":\"P-13362\",\"ACCOUNTID\":\"ACC-3342\",\"CUSIP\":\"AAPL\",\"QUANTITY\":83382}}}"}, + {"key":"accounts:ACCOUNTID:ACC-4167","timestamp":"2025-09-10T16:16:10.893836Z","event":"scan","type":"json","value":"{\"ACCOUNTID\":\"ACC-4167\",\"ACC_NAME\":\"Test Corporation Beta\",\"MANAGER\":\"Manager Beta\",\"COMMISSION_RATE\":3,\"CASH_BALANCE\":263920,\"ACC_VALUE\":2314973,\"DAY_CHANGE\":151377,\"UNREALIZED_GAIN_LOSS\":311809,\"MANAGER_FNAME\":\"Manager\",\"MANAGER_LNAME\":\"Beta\",\"Positions\":{\"P-16621\":{\"POSITIONID\":\"P-16621\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"AAPL\",\"QUANTITY\":27012},\"P-16620\":{\"POSITIONID\":\"P-16620\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"TSLA\",\"QUANTITY\":6026},\"P-16624\":{\"POSITIONID\":\"P-16624\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"CVS\",\"QUANTITY\":46269},\"P-16622\":{\"POSITIONID\":\"P-16622\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"AAPL\",\"QUANTITY\":14136},\"P-16623\":{\"POSITIONID\":\"P-16623\",\"ACCOUNTID\":\"ACC-4167\",\"CUSIP\":\"META\",\"QUANTITY\":54401}}}"}, + {"key":"accounts:ACCOUNTID:ACC-3230","timestamp":"2025-09-10T16:16:10.893844Z","event":"scan","type":"json","value":"{\"ACCOUNTID\":\"ACC-3230\",\"ACC_NAME\":\"Test Corporation Gamma\",\"MANAGER\":\"Manager Gamma\",\"COMMISSION_RATE\":4,\"CASH_BALANCE\":334454,\"ACC_VALUE\":9357169,\"DAY_CHANGE\":-81901,\"UNREALIZED_GAIN_LOSS\":-140338,\"MANAGER_FNAME\":\"Manager\",\"MANAGER_LNAME\":\"Gamma\",\"Positions\":{\"P-12897\":{\"POSITIONID\":\"P-12897\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"TSLA\",\"QUANTITY\":27382},\"P-12900\":{\"POSITIONID\":\"P-12900\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"TSLA\",\"QUANTITY\":4083},\"P-12899\":{\"POSITIONID\":\"P-12899\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"TSLA\",\"QUANTITY\":79731},\"P-12898\":{\"POSITIONID\":\"P-12898\",\"ACCOUNTID\":\"ACC-3230\",\"CUSIP\":\"AAPL\",\"QUANTITY\":38186}}}"} ] \ No newline at end of file