diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml
index 458d5ced..8d0d7797 100644
--- a/.github/workflows/_build.yml
+++ b/.github/workflows/_build.yml
@@ -11,6 +11,7 @@ jobs:
strategy:
matrix:
java-version:
+ - "21"
- "11"
- "8"
distribution:
diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml
index fe4bed41..bca2f454 100644
--- a/.github/workflows/_test-integrations.yml
+++ b/.github/workflows/_test-integrations.yml
@@ -12,6 +12,7 @@ jobs:
strategy:
matrix:
java-version:
+ - "21"
- "11"
- "8"
distribution:
diff --git a/pom.xml b/pom.xml
index 8366754b..97aefa4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,6 +133,20 @@
8
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${org.apache.maven.surfire.version}
+
+
+ **/*Test.java
+
+
+ ${surefire.addOpens}
+ -Djunit.jupiter.extensions.autodetection.enabled=true
+
+
+
org.sonatype.central
central-publishing-maven-plugin
@@ -148,6 +162,22 @@
+
+ jdk-9-plus
+
+ [9,)
+
+
+
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens java.base/java.net=ALL-UNNAMED
+ --add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED
+ --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+
+
+
release
@@ -178,16 +208,6 @@
-
- org.apache.maven.plugins
- maven-surefire-plugin
- ${org.apache.maven.surfire.version}
-
-
- **/*Test.java
-
-
-
org.apache.maven.plugins
maven-gpg-plugin
@@ -361,6 +381,18 @@
test
${org.mockito.inline.version}
+
+ net.bytebuddy
+ byte-buddy
+ 1.15.11
+ test
+
+
+ net.bytebuddy
+ byte-buddy-agent
+ 1.15.11
+ test
+
com.github.tomakehurst
wiremock-jre8
@@ -412,12 +444,13 @@
1.8.2
2.2
1.5.3.Final
- 4.6.1
- 4.5.1
+ 4.11.0
+ 4.11.0
0.2.0
1.18.32
2.0.17
UTF-8
+
2.35.2
diff --git a/src/main/java/com/mindee/parsing/v2/field/ObjectField.java b/src/main/java/com/mindee/parsing/v2/field/ObjectField.java
index 8798d6b2..e3c2a12d 100644
--- a/src/main/java/com/mindee/parsing/v2/field/ObjectField.java
+++ b/src/main/java/com/mindee/parsing/v2/field/ObjectField.java
@@ -42,6 +42,97 @@ public LinkedHashMap getSimpleFields() throws IllegalStateE
return simpleFields;
}
+ /**
+ * Retrieves all subfields from the {@code fields} map as a {@link LinkedHashMap} of
+ * {@code ListField} objects, keyed by their field names.
+ *
+ * @return a {@link LinkedHashMap} containing the field names as keys and their corresponding
+ * {@code ListField} instances as values (only includes fields that are list fields)
+ */
+ public LinkedHashMap getListFields() {
+ LinkedHashMap listFields = new LinkedHashMap<>();
+ if (fields != null) {
+ for (String fieldName : fields.keySet()) {
+ DynamicField field = fields.get(fieldName);
+ if (field != null && field.getType() == DynamicField.FieldType.LIST_FIELD) {
+ listFields.put(fieldName, field.getListField());
+ }
+ }
+ }
+ return listFields;
+ }
+
+ /**
+ * Retrieves all subfields from the {@code fields} map as a {@link LinkedHashMap} of
+ * {@code ObjectField} objects, keyed by their field names.
+ *
+ * @return a {@link LinkedHashMap} containing the field names as keys and their corresponding
+ * {@code ObjectField} objects as values
+ */
+ public LinkedHashMap getObjectFields() {
+ LinkedHashMap objectFields = new LinkedHashMap<>();
+ if (fields != null) {
+ for (String fieldName : fields.keySet()) {
+ DynamicField field = fields.get(fieldName);
+ if (field != null && field.getType() == DynamicField.FieldType.OBJECT_FIELD) {
+ objectFields.put(fieldName, field.getObjectField());
+ }
+ }
+ }
+ return objectFields;
+ }
+
+ /**
+ * Retrieves a single sub-field from the {@code fields} map as a {@link SimpleField}.
+ *
+ * @param fieldKey the name/key of the field to retrieve
+ * @return the corresponding {@link SimpleField}, or {@code null} if {@code fields} is
+ * {@code null}
+ * or if no field exists for {@code fieldKey} (depending on {@code fields}' behavior)
+ * @throws IllegalStateException if the referenced field exists but is not of type
+ * {@code SIMPLE_FIELD}
+ */
+ public SimpleField getSimpleField(String fieldKey) throws IllegalStateException {
+ if (fields == null) {
+ return null;
+ }
+ return fields.getSimpleField(fieldKey);
+ }
+
+ /**
+ * Retrieves a list sub-field from the {@code fields} map as a {@link ListField}.
+ *
+ * @param fieldKey the name/key of the field to retrieve
+ * @return the corresponding {@link ListField}, or {@code null} if {@code fields} is
+ * {@code null}
+ * or if no field exists for {@code fieldKey} (depending on {@code fields}' behavior)
+ * @throws IllegalStateException if the referenced field exists but is not of type
+ * {@code LIST_FIELD}
+ */
+ public ListField getListField(String fieldKey) {
+ if (fields == null) {
+ return null;
+ }
+ return fields.getListField(fieldKey);
+ }
+
+ /**
+ * Retrieves an object sub-field from the {@code fields} map as a {@link ObjectField}.
+ *
+ * @param fieldKey the name/key of the field to retrieve
+ * @return the corresponding {@link ObjectField}, or {@code null} if {@code fields} is
+ * {@code null}
+ * or if no field exists for {@code fieldKey} (depending on {@code fields}' behavior)
+ * @throws IllegalStateException if the referenced field exists but is not of type
+ * {@code OBJECT_FIELD}
+ */
+ public ObjectField getObjectField(String fieldKey) {
+ if (fields == null) {
+ return null;
+ }
+ return fields.getObjectField(fieldKey);
+ }
+
@Override
public String toString() {
return "\n" + (fields != null ? fields.toString(1) : "");
diff --git a/src/test/java/com/mindee/MindeeClientV2IT.java b/src/test/java/com/mindee/MindeeClientV2IT.java
index 296690cf..106582b1 100644
--- a/src/test/java/com/mindee/MindeeClientV2IT.java
+++ b/src/test/java/com/mindee/MindeeClientV2IT.java
@@ -87,7 +87,7 @@ void parseFile_emptyMultiPage_mustSucceed() throws IOException, InterruptedExcep
@DisplayName("Filled, single-page image – enqueue & parse must succeed")
void parseFile_filledSinglePage_mustSucceed() throws IOException, InterruptedException {
LocalInputSource source = new LocalInputSource(
- getV2ResourcePath("products/financial_document/default_sample.jpg")
+ getV2ResourcePath("products/extraction/financial_document/default_sample.jpg")
);
InferenceParameters params = InferenceParameters
@@ -135,14 +135,16 @@ void parseFile_filledSinglePage_mustSucceed() throws IOException, InterruptedExc
@DisplayName("Data Schema Replace – enqueue & parse must succeed")
void parseFile_dataSchemaReplace_mustSucceed() throws IOException, InterruptedException {
LocalInputSource source = new LocalInputSource(
- getV2ResourcePath("products/financial_document/default_sample.jpg")
+ getV2ResourcePath("products/extraction/financial_document/default_sample.jpg")
);
InferenceParameters params = InferenceParameters
.builder(modelId)
.rag(false)
.alias("java-integration-test_data-schema-replace")
- .dataSchema(readFileAsString(getV2ResourcePath("inference/data_schema_replace_param.json")))
+ .dataSchema(
+ readFileAsString(getV2ResourcePath("products/extraction/data_schema_replace_param.json"))
+ )
.build();
InferenceResponse response = mindeeClient.enqueueAndGetInference(source, params);
diff --git a/src/test/java/com/mindee/MindeeClientV2Test.java b/src/test/java/com/mindee/MindeeClientV2Test.java
index 67a5a017..d3e41f96 100644
--- a/src/test/java/com/mindee/MindeeClientV2Test.java
+++ b/src/test/java/com/mindee/MindeeClientV2Test.java
@@ -1,9 +1,13 @@
package com.mindee;
import static com.mindee.TestingUtilities.getResourcePath;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atMostOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -87,7 +91,9 @@ void document_getInference_async() throws IOException {
MindeeApiV2 predictable = Mockito.mock(MindeeApiV2.class);
String json = FileUtils
- .readFileToString(getResourcePath("v2/products/financial_document/complete.json").toFile());
+ .readFileToString(
+ getResourcePath("v2/products/extraction/financial_document/complete.json").toFile()
+ );
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
@@ -129,7 +135,7 @@ class DeserializeResponse {
@DisplayName("parses local JSON and exposes correct field values")
void inference_loadsLocally() throws IOException {
LocalResponse localResponse = new LocalResponse(
- getResourcePath("v2/products/financial_document/complete.json")
+ getResourcePath("v2/products/extraction/financial_document/complete.json")
);
InferenceResponse loaded = localResponse.deserializeResponse(InferenceResponse.class);
diff --git a/src/test/java/com/mindee/MindeeSettingsTest.java b/src/test/java/com/mindee/MindeeSettingsTest.java
index 4ee59da1..46f76d69 100644
--- a/src/test/java/com/mindee/MindeeSettingsTest.java
+++ b/src/test/java/com/mindee/MindeeSettingsTest.java
@@ -11,7 +11,7 @@ public class MindeeSettingsTest {
@SetEnvironmentVariable(key = "MINDEE_API_URL", value = "https://example.com")
void setEnvironmentVariablesAndEmptyParams() {
MindeeSettings settings = new MindeeSettings("", "");
- Assertions.assertEquals(settings.getApiKey().orElse(""), "abcd");
- Assertions.assertEquals(settings.getBaseUrl(), "https://example.com");
+ Assertions.assertEquals("abcd", settings.getApiKey().orElse(""));
+ Assertions.assertEquals("https://example.com", settings.getBaseUrl());
}
}
diff --git a/src/test/java/com/mindee/input/LocalResponseV2Test.java b/src/test/java/com/mindee/input/LocalResponseV2Test.java
index b33eab2d..d7066df9 100644
--- a/src/test/java/com/mindee/input/LocalResponseV2Test.java
+++ b/src/test/java/com/mindee/input/LocalResponseV2Test.java
@@ -19,12 +19,12 @@ public class LocalResponseV2Test {
/**
* Real signature using fake secret key.
*/
- String signature = "1df388c992d87897fe61dfc56c444c58fc3c7369c31e2b5fd20d867695e93e85";
+ String signature = "e51bdf80f1a08ed44ee161100fc30a25cb35b4ede671b0a575dc9064a3f5dbf1";
/**
* File which the signature applies to.
*/
- Path filePath = getV2ResourcePath("inference/standard_field_types.json");
+ Path filePath = getV2ResourcePath("products/extraction/standard_field_types.json");
protected void assertLocalResponse(LocalResponse localResponse) {
Assertions.assertNotNull(localResponse.getFile());
diff --git a/src/test/java/com/mindee/parsing/v2/InferenceTest.java b/src/test/java/com/mindee/parsing/v2/InferenceTest.java
index 411617e5..58014332 100644
--- a/src/test/java/com/mindee/parsing/v2/InferenceTest.java
+++ b/src/test/java/com/mindee/parsing/v2/InferenceTest.java
@@ -2,7 +2,13 @@
import static com.mindee.TestingUtilities.getV2ResourcePath;
import static com.mindee.TestingUtilities.readFileAsString;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.mindee.geometry.Point;
import com.mindee.geometry.Polygon;
@@ -40,7 +46,9 @@ class BlankPredictionTest {
@Test
@DisplayName("all properties must be valid")
void asyncPredict_whenEmpty_mustHaveValidProperties() throws IOException {
- InferenceResponse response = loadInference("products/financial_document/blank.json");
+ InferenceResponse response = loadInference(
+ "products/extraction/financial_document/blank.json"
+ );
InferenceFields fields = response.getInference().getResult().getFields();
assertEquals(21, fields.size(), "Expected 21 fields");
@@ -92,7 +100,9 @@ class CompletePredictionTest {
@Test
@DisplayName("every exposed property must be valid and consistent")
void asyncPredict_whenComplete_mustExposeAllProperties() throws IOException {
- InferenceResponse response = loadInference("products/financial_document/complete.json");
+ InferenceResponse response = loadInference(
+ "products/extraction/financial_document/complete.json"
+ );
Inference inference = response.getInference();
assertNotNull(inference);
assertEquals("12345678-1234-1234-1234-123456789abc", inference.getId());
@@ -162,7 +172,7 @@ class DeepNestedFieldsTest {
@Test
@DisplayName("all nested structures must be typed correctly")
void deepNestedFields_mustExposeCorrectTypes() throws IOException {
- InferenceResponse resp = loadInference("inference/deep_nested_fields.json");
+ InferenceResponse resp = loadInference("products/extraction/deep_nested_fields.json");
Inference inf = resp.getInference();
assertNotNull(inf);
@@ -171,9 +181,29 @@ void deepNestedFields_mustExposeCorrectTypes() throws IOException {
assertNotNull(root.get("field_object").getObjectField());
ObjectField fieldObject = root.get("field_object").getObjectField();
+ assertNotNull(fieldObject.getObjectField("sub_object_object"));
InferenceFields lvl1 = fieldObject.getFields();
assertNotNull(lvl1.get("sub_object_list").getListField());
assertNotNull(lvl1.get("sub_object_object").getObjectField());
+ assertEquals(1, lvl1.get("sub_object_object").getObjectField().getListFields().size());
+ assertEquals(1, lvl1.get("sub_object_object").getObjectField().getObjectFields().size());
+ assertEquals(
+ 1,
+ lvl1
+ .get("sub_object_object")
+ .getObjectField()
+ .getListField("sub_object_object_sub_object_list")
+ .getItems()
+ .size()
+ );
+ assertEquals(
+ "value_6",
+ lvl1
+ .get("sub_object_object")
+ .getObjectField()
+ .getSimpleField("sub_object_object_sub_object_simple")
+ .getStringValue()
+ );
ObjectField subObjectObject = lvl1.get("sub_object_object").getObjectField();
InferenceFields lvl2 = subObjectObject.getFields();
@@ -208,7 +238,7 @@ private void testSimpleFieldString(SimpleField field) {
@Test
@DisplayName("simple fields must be recognised")
void standardFieldTypes_mustExposeSimpleFieldValues() throws IOException {
- InferenceResponse response = loadInference("inference/standard_field_types.json");
+ InferenceResponse response = loadInference("products/extraction/standard_field_types.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -265,7 +295,7 @@ void standardFieldTypes_mustExposeSimpleFieldValues() throws IOException {
@Test
@DisplayName("simple list fields must be recognised")
void standardFieldTypes_mustExposeSimpleListFieldValues() throws IOException {
- InferenceResponse response = loadInference("inference/standard_field_types.json");
+ InferenceResponse response = loadInference("products/extraction/standard_field_types.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -308,7 +338,7 @@ private void testObjectSubFieldSimpleString(String fieldName, SimpleField subFie
@Test
@DisplayName("object list fields must be recognised")
void standardFieldTypes_mustExposeObjectListFieldValues() throws IOException {
- InferenceResponse response = loadInference("inference/standard_field_types.json");
+ InferenceResponse response = loadInference("products/extraction/standard_field_types.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -358,7 +388,7 @@ void standardFieldTypes_mustExposeObjectListFieldValues() throws IOException {
@Test
@DisplayName("simple / object / list variants must be recognised")
void standardFieldTypes_mustExposeObjectFieldValues() throws IOException {
- InferenceResponse response = loadInference("inference/standard_field_types.json");
+ InferenceResponse response = loadInference("products/extraction/standard_field_types.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -393,7 +423,7 @@ void standardFieldTypes_mustExposeObjectFieldValues() throws IOException {
@Test
@DisplayName("allow getting fields using generics")
void standardFieldTypes_getWithGenerics() throws IOException {
- InferenceResponse response = loadInference("inference/standard_field_types.json");
+ InferenceResponse response = loadInference("products/extraction/standard_field_types.json");
Inference inference = response.getInference();
assertNotNull(inference);
InferenceFields fields = inference.getResult().getFields();
@@ -429,7 +459,7 @@ void standardFieldTypes_getWithGenerics() throws IOException {
@Test
@DisplayName("confidence and locations must be usable")
void standardFieldTypes_confidenceAndLocations() throws IOException {
- InferenceResponse response = loadInference("inference/standard_field_types.json");
+ InferenceResponse response = loadInference("products/extraction/standard_field_types.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -469,7 +499,7 @@ class RawTextTest {
@Test
@DisplayName("raw texts option must be parsed and exposed")
void rawTexts_mustBeAccessible() throws IOException {
- InferenceResponse response = loadInference("inference/raw_texts.json");
+ InferenceResponse response = loadInference("products/extraction/raw_texts.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -504,7 +534,7 @@ class RagMetadataTest {
@Test
@DisplayName("RAG metadata when matched")
void rag_mustBeFilled_whenMatched() throws IOException {
- InferenceResponse response = loadInference("inference/rag_matched.json");
+ InferenceResponse response = loadInference("products/extraction/rag_matched.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -516,7 +546,7 @@ void rag_mustBeFilled_whenMatched() throws IOException {
@Test
@DisplayName("RAG metadata when not matched")
void rag_mustBeNull_whenNotMatched() throws IOException {
- InferenceResponse response = loadInference("inference/rag_not_matched.json");
+ InferenceResponse response = loadInference("products/extraction/rag_not_matched.json");
Inference inference = response.getInference();
assertNotNull(inference);
@@ -532,8 +562,10 @@ class RstDisplay {
@Test
@DisplayName("rst display must be parsed and exposed")
void rstDisplay_mustBeAccessible() throws IOException {
- InferenceResponse resp = loadInference("inference/standard_field_types.json");
- String rstRef = readFileAsString(getV2ResourcePath("inference/standard_field_types.rst"));
+ InferenceResponse resp = loadInference("products/extraction/standard_field_types.json");
+ String rstRef = readFileAsString(
+ getV2ResourcePath("products/extraction/standard_field_types.rst")
+ );
Inference inference = resp.getInference();
assertNotNull(inference);
assertEquals(rstRef, resp.getInference().toString());
@@ -546,7 +578,7 @@ class TextContextTest {
@Test
@DisplayName("should be present and true when enabled")
void textContext_mustBePresentAndTrue() throws IOException {
- InferenceResponse resp = loadInference("inference/text_context_enabled.json");
+ InferenceResponse resp = loadInference("products/extraction/text_context_enabled.json");
Inference inference = resp.getInference();
assertNotNull(inference);
assertTrue(inference.getActiveOptions().getTextContext());
@@ -559,7 +591,7 @@ class DataSchemaTest {
@Test
@DisplayName("should be present and true when enabled")
void textContext_mustBePresentAndTrue() throws IOException {
- InferenceResponse resp = loadInference("inference/data_schema_replace.json");
+ InferenceResponse resp = loadInference("products/extraction/data_schema_replace.json");
Inference inference = resp.getInference();
assertNotNull(inference);
InferenceFields fields = inference.getResult().getFields();
diff --git a/src/test/resources b/src/test/resources
index 0c51e1d3..37f2e3de 160000
--- a/src/test/resources
+++ b/src/test/resources
@@ -1 +1 @@
-Subproject commit 0c51e1d3e2258404c44280f25f4951ba6fe27324
+Subproject commit 37f2e3de48918e3b1a0e4604a9292aaeae05c637