Repository: xlate/staedi
Pull request #338 (2023-03-23 00:50:10)

Implement additional methods for JsonParsers

sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=xlate_staedi&pullRequest=338)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL)

[![74.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '74.5%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_coverage&view=list) [74.5% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=xlate_staedi&pullRequest=338)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL)

[![97.9%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90-16px.png '97.9%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_coverage&view=list) [97.9% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=xlate_staedi&pullRequest=338)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=338&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=338&resolved=false&types=CODE_SMELL)

[![100.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100-16px.png '100.0%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=338&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/pom.xml b/pom.xml
index b77eddb..7527693 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,6 +64,7 @@
     <version.junit-jupiter>5.9.2</version.junit-jupiter>
     <version.xmlunit>2.9.1</version.xmlunit>
     <version.jsonassert>1.5.1</version.jsonassert>
+    <version.parsson>1.1.1</version.parsson>
   </properties>
 
   <build>
@@ -224,6 +225,7 @@
       <dependency>
         <groupId>org.glassfish</groupId>
         <artifactId>javax.json</artifactId>
+        <classifier>module</classifier>
         <version>${version.javax.json.api}</version>
       </dependency>
       <dependency>
@@ -241,6 +243,11 @@
         <artifactId>jsonassert</artifactId>
         <version>${version.jsonassert}</version>
       </dependency>
+      <dependency>
+        <groupId>org.eclipse.parsson</groupId>
+        <artifactId>parsson</artifactId>
+        <version>${version.parsson}</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
@@ -290,6 +297,12 @@
     <dependency>
       <groupId>org.glassfish</groupId>
       <artifactId>javax.json</artifactId>
+      <classifier>module</classifier>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.parsson</groupId>
+      <artifactId>parsson</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -317,6 +330,11 @@
             <artifactId>jakarta.json-api</artifactId>
             <version>2.0.1</version>
           </dependency>
+          <dependency>
+            <groupId>org.eclipse.parsson</groupId>
+            <artifactId>parsson</artifactId>
+            <version>1.0.2</version>
+          </dependency>
           <dependency>
             <groupId>jakarta.xml.bind</groupId>
             <artifactId>jakarta.xml.bind-api</artifactId>
diff --git a/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJakartaJsonParser.java b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJakartaJsonParser.java
index 43f5edd..ace495b 100644
--- a/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJakartaJsonParser.java
+++ b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJakartaJsonParser.java
@@ -22,6 +22,9 @@ import io.xlate.edi.stream.EDIStreamReader;
 final class StaEDIJakartaJsonParser extends StaEDIJsonParser
         implements jakarta.json.stream.JsonParser, jakarta.json.stream.JsonLocation {
 
+    static final jakarta.json.stream.JsonParser.Event[] eventMap = mapEvents(jakarta.json.stream.JsonParser.Event.class);
+    static final jakarta.json.spi.JsonProvider jsonProvider = jakarta.json.spi.JsonProvider.provider();
+
     StaEDIJakartaJsonParser(EDIStreamReader ediReader, Map<String, Object> properties) {
         super(ediReader, properties);
     }
@@ -43,29 +46,62 @@ final class StaEDIJakartaJsonParser extends StaEDIJsonParser
 
     @Override
     public jakarta.json.stream.JsonParser.Event next() {
-        final StaEDIJsonParser.Event next = nextEvent();
+        return eventMap[nextEvent().ordinal()];
+    }
 
-        switch (next) {
-        case END_ARRAY:
-            return jakarta.json.stream.JsonParser.Event.END_ARRAY;
-        case END_OBJECT:
-            return jakarta.json.stream.JsonParser.Event.END_OBJECT;
-        case KEY_NAME:
-            return jakarta.json.stream.JsonParser.Event.KEY_NAME;
-        case START_ARRAY:
-            return jakarta.json.stream.JsonParser.Event.START_ARRAY;
+    @Override
+    public jakarta.json.JsonValue getValue() {
+        assertEventSet("getValue illegal when data stream has not yet been read");
+
+        switch (currentEvent) {
         case START_OBJECT:
-            return jakarta.json.stream.JsonParser.Event.START_OBJECT;
+            return getObject();
+        case START_ARRAY:
+            return getArray();
         case VALUE_NULL:
-            return jakarta.json.stream.JsonParser.Event.VALUE_NULL;
+            return jakarta.json.JsonValue.NULL;
         case VALUE_NUMBER:
-            return jakarta.json.stream.JsonParser.Event.VALUE_NUMBER;
+            return isIntegralNumber() ?
+                    jsonProvider.createValue(getLong()) :
+                    jsonProvider.createValue(getBigDecimal());
         case VALUE_STRING:
-            return jakarta.json.stream.JsonParser.Event.VALUE_STRING;
+        case KEY_NAME:
+            return jsonProvider.createValue(getString());
         default:
-            // JSON 'true' and 'false' are not expected in the EDI stream
-            throw new jakarta.json.stream.JsonParsingException(MSG_UNEXPECTED + next, this);
+            throw new IllegalStateException("getValue illegal when at current position");
+        }
+    }
+
+    @Override
+    public jakarta.json.JsonArray getArray() {
+        assertEvent(StaEDIJsonParser.Event.START_ARRAY, current -> "getArray illegal when not at start of array");
+
+        jakarta.json.JsonArrayBuilder builder = jsonProvider.createArrayBuilder();
+
+        while (nextEvent() != StaEDIJsonParser.Event.END_ARRAY) {
+            builder.add(getValue());
         }
+
+        return builder.build();
     }
 
+    @Override
+    public jakarta.json.JsonObject getObject() {
+        assertEvent(StaEDIJsonParser.Event.START_OBJECT, current -> "getObject illegal when not at start of object");
+
+        jakarta.json.JsonObjectBuilder builder = jsonProvider.createObjectBuilder();
+
+        StaEDIJsonParser.Event next;
+        String key = null;
+
+        while ((next = nextEvent()) != StaEDIJsonParser.Event.END_OBJECT) {
+            if (next == StaEDIJsonParser.Event.KEY_NAME) {
+                key = getString();
+            } else {
+                builder.add(key, getValue());
+            }
+        }
+
+        return builder.build();
+    }
 }
diff --git a/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJavaxJsonParser.java b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJavaxJsonParser.java
index 088970e..c3f7027 100644
--- a/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJavaxJsonParser.java
+++ b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJavaxJsonParser.java
@@ -22,6 +22,9 @@ import io.xlate.edi.stream.EDIStreamReader;
 final class StaEDIJavaxJsonParser extends StaEDIJsonParser
         implements javax.json.stream.JsonParser, javax.json.stream.JsonLocation {
 
+    static final javax.json.stream.JsonParser.Event[] eventMap = mapEvents(javax.json.stream.JsonParser.Event.class);
+    static final javax.json.spi.JsonProvider jsonProvider = javax.json.spi.JsonProvider.provider();
+
     StaEDIJavaxJsonParser(EDIStreamReader ediReader, Map<String, Object> properties) {
         super(ediReader, properties);
     }
@@ -43,29 +46,62 @@ final class StaEDIJavaxJsonParser extends StaEDIJsonParser
 
     @Override
     public javax.json.stream.JsonParser.Event next() {
-        final StaEDIJsonParser.Event next = nextEvent();
+        return eventMap[nextEvent().ordinal()];
+    }
 
-        switch (next) {
-        case END_ARRAY:
-            return javax.json.stream.JsonParser.Event.END_ARRAY;
-        case END_OBJECT:
-            return javax.json.stream.JsonParser.Event.END_OBJECT;
-        case KEY_NAME:
-            return javax.json.stream.JsonParser.Event.KEY_NAME;
-        case START_ARRAY:
-            return javax.json.stream.JsonParser.Event.START_ARRAY;
+    @Override
+    public javax.json.JsonValue getValue() {
+        assertEventSet("getValue illegal when data stream has not yet been read");
+
+        switch (currentEvent) {
         case START_OBJECT:
-            return javax.json.stream.JsonParser.Event.START_OBJECT;
+            return getObject();
+        case START_ARRAY:
+            return getArray();
         case VALUE_NULL:
-            return javax.json.stream.JsonParser.Event.VALUE_NULL;
+            return javax.json.JsonValue.NULL;
         case VALUE_NUMBER:
-            return javax.json.stream.JsonParser.Event.VALUE_NUMBER;
+            return isIntegralNumber() ?
+                    jsonProvider.createValue(getLong()) :
+                    jsonProvider.createValue(getBigDecimal());
         case VALUE_STRING:
-            return javax.json.stream.JsonParser.Event.VALUE_STRING;
+        case KEY_NAME:
+            return jsonProvider.createValue(getString());
         default:
-            // JSON 'true' and 'false' are not expected in the EDI stream
-            throw new javax.json.stream.JsonParsingException(MSG_UNEXPECTED + next, this);
+            throw new IllegalStateException("getValue illegal when at current position");
+        }
+    }
+
+    @Override
+    public javax.json.JsonArray getArray() {
+        assertEvent(StaEDIJsonParser.Event.START_ARRAY, current -> "getArray illegal when not at start of array");
+
+        javax.json.JsonArrayBuilder builder = jsonProvider.createArrayBuilder();
+
+        while (nextEvent() != StaEDIJsonParser.Event.END_ARRAY) {
+            builder.add(getValue());
         }
+
+        return builder.build();
     }
 
+    @Override
+    public javax.json.JsonObject getObject() {
+        assertEvent(StaEDIJsonParser.Event.START_OBJECT, current -> "getObject illegal when not at start of object");
+
+        javax.json.JsonObjectBuilder builder = jsonProvider.createObjectBuilder();
+
+        StaEDIJsonParser.Event next;
+        String key = null;
+
+        while ((next = nextEvent()) != StaEDIJsonParser.Event.END_OBJECT) {
+            if (next == StaEDIJsonParser.Event.KEY_NAME) {
+                key = getString();
+            } else {
+                builder.add(key, getValue());
+            }
+        }
+
+        return builder.build();
+    }
 }
diff --git a/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJsonParser.java b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJsonParser.java
index 3bed270..f34b315 100644
--- a/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJsonParser.java
+++ b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJsonParser.java
@@ -16,13 +16,17 @@
 package io.xlate.edi.internal.stream.json;
 
 import java.io.IOException;
+import java.lang.reflect.Array;
 import java.math.BigDecimal;
 import java.text.DecimalFormat;
 import java.text.ParsePosition;
 import java.util.ArrayDeque;
 import java.util.Map;
 import java.util.Queue;
+import java.util.function.Function;
+import java.util.function.ToIntFunction;
 import java.util.logging.Logger;
+import java.util.stream.Stream;
 
 import io.xlate.edi.internal.stream.Configurable;
 import io.xlate.edi.schema.EDIReference;
@@ -117,6 +121,16 @@ abstract class StaEDIJsonParser implements Configurable {
         this.elementsAsObject = getProperty(EDIInputFactory.JSON_OBJECT_ELEMENTS, Boolean::parseBoolean, false);
     }
 
+    @SuppressWarnings("unchecked")
+    static <E extends Enum<E>> E[] mapEvents(Class<E> eventType) {
+        ToIntFunction<E> ordinal = e -> Event.valueOf(e.name()).ordinal();
+
+        return Stream.of(eventType.getEnumConstants())
+                .sorted((c1, c2) -> Integer.compare(ordinal.applyAsInt(c1), ordinal.applyAsInt(c2)))
+                .map(eventType::cast)
+                .toArray(size -> (E[]) Array.newInstance(eventType, size));
+    }
+
     protected abstract RuntimeException newJsonException(String message, Throwable cause);
 
     protected abstract RuntimeException newJsonParsingException(String message, Throwable cause);
@@ -301,14 +315,24 @@ abstract class StaEDIJsonParser implements Configurable {
         }
     }
 
-    void assertEventValueNumber() {
+    void assertEventSet(String message) {
+        if (this.currentEvent == null) {
+            throw new IllegalStateException(message);
+        }
+    }
+
+    void assertEvent(Event required, Function<Event, String> message) {
         final Event current = this.currentEvent;
 
-        if (current != Event.VALUE_NUMBER) {
-            throw new IllegalStateException("Unable to get number value for event [" + current + ']');
+        if (current != required) {
+            throw new IllegalStateException(message.apply(current));
         }
     }
 
+    void assertEventValueNumber() {
+        assertEvent(Event.VALUE_NUMBER, current -> "Unable to get number value for event [" + current + ']');
+    }
+
     void assertEventValueString() {
         final Event current = this.currentEvent;
 


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/stream/json/StaEDIJsonParserTest.java b/src/test/java/io/xlate/edi/internal/stream/json/StaEDIJsonParserTest.java
index fa56e49..09fa69d 100644
--- a/src/test/java/io/xlate/edi/internal/stream/json/StaEDIJsonParserTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/json/StaEDIJsonParserTest.java
@@ -20,8 +20,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import javax.json.Json;
-import javax.json.stream.JsonGenerator;
+import jakarta.json.Json;
+import jakarta.json.stream.JsonGenerator;
+import jakarta.json.stream.JsonParser;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -183,6 +184,59 @@ class StaEDIJsonParserTest {
         JSONAssert.assertEquals(String.join("", expected), buffer.toString(), true);
     }
 
+    @ParameterizedTest
+    @CsvSource({
+        "jakarta.json.stream.JsonParser, jakarta.json.JsonObject, /x12/005010/837.xml,                            false, true,  /x12/sample837-original-object-elements.json",
+        "jakarta.json.stream.JsonParser, jakarta.json.JsonObject, /x12/005010/837.xml,                            true,  true,  /x12/sample837-original-object-elements.json",
+        "jakarta.json.stream.JsonParser, jakarta.json.JsonObject, /x12/005010/837-hierarchical-level-enabled.xml, true,  true,  /x12/sample837-original-object-elements-nestHL.json",
+        "jakarta.json.stream.JsonParser, jakarta.json.JsonObject, /x12/005010/837-hierarchical-level-enabled.xml, false, true,  /x12/sample837-original-object-elements.json",
+        "jakarta.json.stream.JsonParser, jakarta.json.JsonObject, /x12/005010/837-hierarchical-level-enabled.xml, true,  false, /x12/sample837-original-nestHL.json",
+        "jakarta.json.stream.JsonParser, jakarta.json.JsonObject, /x12/005010/837-hierarchical-level-enabled.xml, false, false, /x12/sample837-original.json",
+
+        "javax.json.stream.JsonParser,   javax.json.JsonObject,   /x12/005010/837.xml,                            false, true,  /x12/sample837-original-object-elements.json",
+        "javax.json.stream.JsonParser,   javax.json.JsonObject,   /x12/005010/837.xml,                            true,  true,  /x12/sample837-original-object-elements.json",
+        "javax.json.stream.JsonParser,   javax.json.JsonObject,   /x12/005010/837-hierarchical-level-enabled.xml, true,  true,  /x12/sample837-original-object-elements-nestHL.json",
+        "javax.json.stream.JsonParser,   javax.json.JsonObject,   /x12/005010/837-hierarchical-level-enabled.xml, false, true,  /x12/sample837-original-object-elements.json",
+        "javax.json.stream.JsonParser,   javax.json.JsonObject,   /x12/005010/837-hierarchical-level-enabled.xml, true,  false, /x12/sample837-original-nestHL.json",
+        "javax.json.stream.JsonParser,   javax.json.JsonObject,   /x12/005010/837-hierarchical-level-enabled.xml, false, false, /x12/sample837-original.json"
+    })
+    <P, O> void testInputAsJsonObject(Class<P> parserInterface, Class<O> resultInterface, String schemaPath, boolean nestHL, boolean objectElements, String expectedResource) throws Throwable {
+        ediReaderConfig.put(EDIInputFactory.EDI_NEST_HIERARCHICAL_LOOPS, nestHL);
+        ediReaderConfig.put(EDIInputFactory.JSON_OBJECT_ELEMENTS, objectElements);
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, !objectElements);
+
+        setupReader(factory, "/x12/sample837-original.edi", schemaPath);
+        P jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
+
+        String eventName = invoke(jsonParser, "next", Object.class).toString();
+        assertEquals("START_OBJECT", eventName);
+
+        O result = invoke(jsonParser, "getObject", resultInterface);
+
+        JsonParser parser = Json.createParser(getClass().getResourceAsStream(expectedResource));
+        parser.next();
+
+        assertEquals(parser.getObject().toString(), result.toString());
+        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> invoke(jsonParser, "getValue", resultInterface));
+        assertEquals("getValue illegal when at current position", thrown.getMessage());
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+        "jakarta.json.stream.JsonParser, getArray,  getArray illegal when not at start of array",
+        "jakarta.json.stream.JsonParser, getObject, getObject illegal when not at start of object",
+        "jakarta.json.stream.JsonParser, getValue,  getValue illegal when data stream has not yet been read",
+        "javax.json.stream.JsonParser,   getArray,  getArray illegal when not at start of array",
+        "javax.json.stream.JsonParser,   getObject, getObject illegal when not at start of object",
+        "javax.json.stream.JsonParser,   getValue,  getValue illegal when data stream has not yet been read"
+    })
+    void testGetStructureIllegalAtStart(Class<?> parserInterface, String structureMethod, String message) throws Throwable {
+        setupReader(factory, "/x12/simple810.edi", "/x12/EDISchema810.xml");
+        Object jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
+        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> invoke(jsonParser, structureMethod, Object.class));
+        assertEquals(message, thrown.getMessage());
+    }
+
     @ParameterizedTest
     @ValueSource(
             classes = {
diff --git a/src/test/java/io/xlate/edi/internal/wiki/ReadFuncAcknowledgementTest.java b/src/test/java/io/xlate/edi/internal/wiki/ReadFuncAcknowledgementTest.java
index 4645c29..0b9d5d5 100644
--- a/src/test/java/io/xlate/edi/internal/wiki/ReadFuncAcknowledgementTest.java
+++ b/src/test/java/io/xlate/edi/internal/wiki/ReadFuncAcknowledgementTest.java
@@ -1,10 +1,12 @@
 package io.xlate.edi.internal.wiki;
 
-// import java.io.FileInputStream;
 import java.io.InputStream;
 import java.util.ArrayDeque;
 import java.util.Collections;
 import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
 import javax.json.Json;
 import javax.json.JsonArrayBuilder;
@@ -30,14 +32,22 @@ class ReadFuncAcknowledgementTest {
         EDIInputFactory factory = EDIInputFactory.newFactory();
         JsonObject result = null;
 
-        //InputStream stream = new FileInputStream("simple997.edi");
+        // Any InputStream can be used to create an `EDIStreamReader`
         try (InputStream stream = getClass().getResource("/x12/simple997.edi").openStream();
-                EDIStreamReader reader = factory.createEDIStreamReader(stream)) {
+             EDIStreamReader reader = factory.createEDIStreamReader(stream)) {
 
             EDIStreamEvent event;
             boolean transactionBeginSegment = false;
             Deque<JsonObjectBuilder> buildStack = new ArrayDeque<>();
             JsonObjectBuilder builder = null;
+            JsonArrayBuilder transactions = null;
+
+            Deque<Set<String>> activeLoops = new ArrayDeque<>();
+            Set<String> peerLoops;
+
+            Map<String, JsonArrayBuilder> loopBuilders = new HashMap<>();
+            JsonArrayBuilder currentLoop;
+
             JsonArrayBuilder segmentBuilder = null;
             JsonArrayBuilder compositeBuilder = null;
 
@@ -46,39 +56,57 @@ class ReadFuncAcknowledgementTest {
 
                 switch (event) {
                 case START_INTERCHANGE:
+                    // Called at the beginning of the EDI stream once the X12 dialect is confirmed
                     builder = Json.createObjectBuilder();
                     buildStack.offer(builder);
                     break;
                 case END_INTERCHANGE:
+                    // Called following the IEA segment
                     result = builder.build();
                     break;
 
                 case START_GROUP:
+                    // Called prior to the start of the GS segment
                     builder = Json.createObjectBuilder();
                     buildStack.offer(builder);
+                    transactions = Json.createArrayBuilder();
                     break;
                 case END_GROUP:
+                    // Called following the GE segment
                     JsonObjectBuilder groupBuilder = buildStack.removeLast();
+                    groupBuilder.add("TRANSACTIONS", transactions);
                     builder = buildStack.peekLast();
                     builder.add(reader.getReferenceCode(), groupBuilder);
                     break;
 
                 case START_TRANSACTION:
+                    // Called prior to the start of the ST segment
                     builder = Json.createObjectBuilder();
                     buildStack.offer(builder);
+                    // Set a boolean so that when the ST segment is complete, our code
+                    // can set a transaction schema to use with the reader. This boolean
+                    // allows the END_SEGMENT code to know the current context of the
+                    // segment.
                     transactionBeginSegment = true;
                     break;
                 case END_TRANSACTION:
+                    // Called following the SE segment
                     JsonObjectBuilder transactionBuilder = buildStack.removeLast();
+                    transactions.add(transactionBuilder);
                     builder = buildStack.peekLast();
-                    builder.add(reader.getReferenceCode(), transactionBuilder);
                     break;
 
                 case START_LOOP:
+                    // Called before the start of the segment that begins a loop.
+                    // The loop's `code` from the schema can be obtained by a call
+                    // to `reader.getReferenceCode()`
                     builder = Json.createObjectBuilder();
                     buildStack.offer(builder);
                     break;
                 case END_LOOP:
+                    // Called following the end of the segment that ends a loop.
+                    // The loop's `code` from the schema can be obtained by a call
+                    // to `reader.getReferenceCode()`
                     JsonObjectBuilder loopBuilder = buildStack.removeLast();
                     builder = buildStack.peekLast();
                     builder.add(reader.getReferenceCode(), loopBuilder);
@@ -90,7 +118,12 @@ class ReadFuncAcknowledgementTest {
 
                 case END_SEGMENT:
                     if (transactionBeginSegment) {
+                        /*
+                         * At the end of the ST segment, load the schema for use to validate
+                         * the transaction.
+                         */
                         SchemaFactory schemaFactory = SchemaFactory.newFactory();
+                        // Any InputStream or URL can be used to create a `Schema`
                         Schema schema = schemaFactory.createSchema(getClass().getResource("/x12/EDISchema997.xml"));
                         reader.setTransactionSchema(schema);
                     }