Repository: xlate/staedi
Pull request #343 (2023-03-30 00:31:43)

Add Jackson JSON parser implementation

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=343)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&resolved=false&types=CODE_SMELL)

[![40.1%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '40.1%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_coverage&view=list) [40.1% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&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=343&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_duplicated_lines_density&view=list)


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=343)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&resolved=false&types=CODE_SMELL)

[![72.4%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '72.4%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_coverage&view=list) [72.4% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&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=343&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_duplicated_lines_density&view=list)


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=343)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&resolved=false&types=CODE_SMELL)

[![62.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '62.5%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_coverage&view=list) [62.5% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&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=343&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&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=343)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&resolved=false&types=CODE_SMELL)

[![91.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90-16px.png '91.7%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_coverage&view=list) [91.7% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&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=343&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&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=343)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=343&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=343&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=343&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=343&resolved=false&types=CODE_SMELL)

[![91.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90-16px.png '91.7%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_coverage&view=list) [91.7% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&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=343&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=343&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/pom.xml b/pom.xml
index 8ce15a7..ae2708b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,6 +70,22 @@
   </properties>
 
   <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+        <excludes>
+          <exclude>**/project.properties</exclude>
+        </excludes>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+        <includes>
+          <include>**/project.properties</include>
+        </includes>
+      </resource>
+    </resources>
     <pluginManagement>
       <plugins>
         <plugin>
@@ -250,6 +266,11 @@
         <artifactId>parsson</artifactId>
         <version>${version.parsson}</version>
       </dependency>
+      <dependency>
+        <groupId>com.fasterxml.jackson.core</groupId>
+        <artifactId>jackson-databind</artifactId>
+        <version>${version.jackson}</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
@@ -307,6 +328,11 @@
       <artifactId>parsson</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <scope>test</scope>
+    </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/JsonParserFactory.java b/src/main/java/io/xlate/edi/internal/stream/json/JsonParserFactory.java
index 8ab2ddc..21a2464 100644
--- a/src/main/java/io/xlate/edi/internal/stream/json/JsonParserFactory.java
+++ b/src/main/java/io/xlate/edi/internal/stream/json/JsonParserFactory.java
@@ -35,6 +35,9 @@ public final class JsonParserFactory {
         case "javax.json.stream.JsonParser":
             parser = (J) new StaEDIJavaxJsonParser(reader, properties);
             break;
+        case "com.fasterxml.jackson.core.JsonParser":
+            parser = (J) new StaEDIJacksonJsonParser(reader, properties);
+            break;
         default:
             throw new IllegalArgumentException("Unsupported JSON parser type: " + type);
         }
diff --git a/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJacksonJsonParser.java b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJacksonJsonParser.java
new file mode 100644
index 0000000..0aaef36
--- /dev/null
+++ b/src/main/java/io/xlate/edi/internal/stream/json/StaEDIJacksonJsonParser.java
@@ -0,0 +1,434 @@
+package io.xlate.edi.internal.stream.json;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+
+import com.fasterxml.jackson.core.Base64Variant;
+import com.fasterxml.jackson.core.JsonLocation;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonStreamContext;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.base.ParserMinimalBase;
+import com.fasterxml.jackson.core.io.ContentReference;
+
+import io.xlate.edi.internal.stream.json.StaEDIJsonParser.Event;
+import io.xlate.edi.stream.EDIStreamReader;
+
+public class StaEDIJacksonJsonParser extends ParserMinimalBase {
+
+    private static final Version VERSION;
+    private static final BigDecimal MIN_LONG = BigDecimal.valueOf(Long.MIN_VALUE);
+    private static final BigDecimal MAX_LONG = BigDecimal.valueOf(Long.MAX_VALUE);
+
+    private static final BigDecimal MIN_INTEGER = BigDecimal.valueOf(Integer.MIN_VALUE);
+    private static final BigDecimal MAX_INTEGER = BigDecimal.valueOf(Integer.MAX_VALUE);
+
+    private static final BigDecimal MIN_DOUBLE = BigDecimal.valueOf(-Double.MAX_VALUE);
+    private static final BigDecimal MAX_DOUBLE = BigDecimal.valueOf(Double.MAX_VALUE);
+
+    private static final BigDecimal MIN_FLOAT = BigDecimal.valueOf(-Float.MAX_VALUE);
+    private static final BigDecimal MAX_FLOAT = BigDecimal.valueOf(Float.MAX_VALUE);
+
+    private StaEDIJsonParser<JsonParseException> parser;
+    private ObjectCodec codec;
+    private final Deque<StaEDIJsonStreamContext> context = new ArrayDeque<>();
+    private StaEDIJsonStreamContext pendingContext = null;
+
+    StaEDIJacksonJsonParser(EDIStreamReader ediReader, Map<String, Object> properties) {
+        parser = new JacksonJsonParser(ediReader, properties);
+        context.addLast(new StaEDIJsonStreamContext(JsonStreamContext.TYPE_ROOT, null));
+    }
+
+    /* test */ boolean hasNext() throws IOException {
+        return parser.hasNext();
+    }
+
+    /* test */ JsonToken next() throws IOException {
+        return nextToken();
+    }
+
+    /* test */ String getString() throws IOException {
+        return getText();
+    }
+
+    /* test */ int getInt() {
+        return getIntValue();
+    }
+
+    /* test */ JsonLocation getLocation() {
+        return getTokenLocation();
+    }
+
+    @Override
+    public JsonToken nextToken() throws IOException {
+        if (!parser.hasNext()) {
+            return null;
+        }
+
+        if (pendingContext != null) {
+            context.add(pendingContext);
+            pendingContext = null;
+        }
+
+        final Event next = parser.nextEvent();
+        final JsonToken token;
+
+        switch (next) {
+        case END_ARRAY:
+            context.removeLast();
+            token = JsonToken.END_ARRAY;
+            break;
+        case END_OBJECT:
+            context.removeLast();
+            token = JsonToken.END_OBJECT;
+            break;
+        case KEY_NAME:
+            context.getLast().incrementIndex();
+            context.getLast().setCurrentName(getText());
+            token = JsonToken.FIELD_NAME;
+            break;
+        case START_ARRAY:
+            context.getLast().incrementIndexIfArray();
+            pendingContext = new StaEDIJsonStreamContext(JsonStreamContext.TYPE_ARRAY, context.getLast());
+            token = JsonToken.START_ARRAY;
+            break;
+        case START_OBJECT:
+            context.getLast().incrementIndexIfArray();
+            pendingContext = new StaEDIJsonStreamContext(JsonStreamContext.TYPE_OBJECT, context.getLast());
+            token = JsonToken.START_OBJECT;
+            break;
+        case VALUE_NULL:
+            context.getLast().incrementIndexIfArray();
+            token = JsonToken.VALUE_NULL;
+            break;
+        case VALUE_NUMBER:
+            context.getLast().incrementIndexIfArray();
+            if (parser.isIntegralNumber()) {
+                token = JsonToken.VALUE_NUMBER_INT;
+            } else {
+                token = JsonToken.VALUE_NUMBER_FLOAT;
+            }
+            break;
+        case VALUE_STRING:
+            context.getLast().incrementIndexIfArray();
+            token = JsonToken.VALUE_STRING;
+            break;
+        default:
+            throw new JsonParseException(this, StaEDIJsonParser.MSG_UNEXPECTED + next);
+        }
+
+        super._currToken = token;
+
+        return token;
+    }
+
+    @Override
+    public void close() throws IOException {
+        parser.close();
+    }
+
+    @Override
+    public String getText() throws IOException {
+        return parser.getString();
+    }
+
+    @Override
+    public char[] getTextCharacters() throws IOException {
+        return parser.ediReader.getTextCharacters();
+    }
+
+    @Override
+    public int getTextLength() throws IOException {
+        return parser.ediReader.getTextLength();
+    }
+
+    @Override
+    public int getTextOffset() throws IOException {
+        return parser.ediReader.getTextStart();
+    }
+
+    @Override
+    public BigInteger getBigIntegerValue() throws IOException {
+        return parser.getBigDecimal().toBigInteger();
+    }
+
+    @Override
+    public ObjectCodec getCodec() {
+        return this.codec;
+    }
+
+    @Override
+    public void setCodec(ObjectCodec c) {
+        this.codec = c;
+    }
+
+    @Override
+    public JsonLocation getCurrentLocation() {
+        return getTokenLocation();
+    }
+
+    @Override
+    public JsonLocation getTokenLocation() {
+        return new JacksonJsonLocation(parser.getStreamOffset(), parser.getLineNumber(), parser.getColumnNumber());
+    }
+
+    @Override
+    protected void _handleEOF() throws JsonParseException {
+        // Not supported, do nothing
+    }
+
+    @Override
+    public boolean isClosed() {
+        return parser.closed;
+    }
+
+    @Override
+    public JsonStreamContext getParsingContext() {
+        return context.getLast().deepCopy();
+    }
+
+    @Override
+    public String getCurrentName() throws IOException {
+        return context.getLast().getCurrentName();
+    }
+
+    @Override
+    public void overrideCurrentName(String name) {
+        // Not supported, do nothing
+    }
+
+    @Override
+    public boolean hasTextCharacters() {
+        switch (parser.currentEvent) {
+        case KEY_NAME:
+        case VALUE_STRING:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    @Override
+    public byte[] getBinaryValue(Base64Variant b64variant) throws IOException {
+        return parser.currentBinaryValue.toByteArray();
+    }
+
+    @Override
+    public Version version() {
+        return VERSION;
+    }
+
+    static <T extends Number & Comparable<T>> boolean between(T value, T min, T max) {
+        return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
+    }
+
+    @Override
+    public Number getNumberValue() throws IOException {
+        if (parser.currentEvent != Event.VALUE_NUMBER) {
+            throw new JsonParseException(this, "Current token is not a number");
+        }
+
+        BigDecimal value = parser.getBigDecimal();
+        Number result;
+
+        switch (getNumberType()) {
+        case BIG_INTEGER:
+            result = value.toBigInteger();
+            break;
+        case DOUBLE:
+            result = value.doubleValue();
+            break;
+        case FLOAT:
+            result = value.floatValue();
+            break;
+        case INT:
+            result = value.intValue();
+            break;
+        case LONG:
+            result = value.longValue();
+            break;
+        default:
+            result = value;
+            break;
+        }
+
+        return result;
+    }
+
+    @Override
+    public NumberType getNumberType() throws IOException {
+        if (parser.currentEvent != Event.VALUE_NUMBER) {
+            return null;
+        }
+
+        BigDecimal value = parser.getBigDecimal();
+        NumberType type;
+
+        if (value.scale() == 0) {
+            if (between(value, MIN_INTEGER, MAX_INTEGER)) {
+                type = NumberType.INT;
+            } else if (between(value, MIN_LONG, MAX_LONG)) {
+                type = NumberType.LONG;
+            } else {
+                type = NumberType.BIG_INTEGER;
+            }
+        } else {
+            int exp = value.precision() - value.scale() - 1;
+
+            if (between(value, MIN_FLOAT, MAX_FLOAT)
+                    && between(exp, Float.MIN_EXPONENT, Float.MAX_EXPONENT)) {
+                type = NumberType.FLOAT;
+            } else if (between(value, MIN_DOUBLE, MAX_DOUBLE)
+                    && between(exp, Double.MIN_EXPONENT, Double.MAX_EXPONENT)) {
+                type = NumberType.DOUBLE;
+            } else {
+                type = NumberType.BIG_DECIMAL;
+            }
+        }
+
+        return type;
+    }
+
+    @Override
+    public int getIntValue() {
+        return parser.getInt();
+    }
+
+    @Override
+    public long getLongValue() {
+        return parser.getLong();
+    }
+
+    @Override
+    public float getFloatValue() {
+        return parser.getBigDecimal().floatValue();
+    }
+
+    @Override
+    public double getDoubleValue() {
+        return parser.getBigDecimal().doubleValue();
+    }
+
+    @Override
+    public BigDecimal getDecimalValue() throws IOException {
+        return parser.getBigDecimal();
+    }
+
+    class JacksonJsonParser extends StaEDIJsonParser<JsonParseException> {
+
+        JacksonJsonParser(EDIStreamReader ediReader, Map<String, Object> properties) {
+            super(ediReader, properties);
+        }
+
+        @Override
+        protected JsonParseException newJsonException(String message, Throwable cause) {
+            return new JsonParseException(StaEDIJacksonJsonParser.this, message, cause);
+        }
+
+        @Override
+        protected JsonParseException newJsonParsingException(String message, Throwable cause) {
+            return newJsonException(message, cause);
+        }
+    }
+
+    static class JacksonJsonLocation extends JsonLocation {
+        private static final long serialVersionUID = 1L;
+
+        public JacksonJsonLocation(long streamOffset, long lineNumber, long columnNumber) {
+            super(ContentReference.unknown(), streamOffset, (int) lineNumber, (int) columnNumber);
+        }
+
+        public long getStreamOffset() {
+            return _totalChars;
+        }
+
+        public long getLineNumber() {
+            return _lineNr;
+        }
+
+        public long getColumnNumber() {
+            return _columnNr;
+        }
+    }
+
+    static class StaEDIJsonStreamContext extends JsonStreamContext {
+
+        StaEDIJsonStreamContext parent;
+        String name;
+
+        StaEDIJsonStreamContext(int type, StaEDIJsonStreamContext parent) {
+            super(type, -1);
+            this.parent = parent;
+        }
+
+        StaEDIJsonStreamContext(StaEDIJsonStreamContext source) {
+            super(source);
+            this.parent = source.parent != null ? source.parent.deepCopy() : null;
+            this.name = source.name;
+        }
+
+        void incrementIndexIfArray() {
+            if (inArray()) {
+                incrementIndex();
+            }
+        }
+
+        void incrementIndex() {
+            this._index++;
+        }
+
+        @Override
+        public JsonStreamContext getParent() {
+            return parent;
+        }
+
+        @Override
+        public String getCurrentName() {
+            return name;
+        }
+
+        public void setCurrentName(String name) {
+            this.name = name;
+        }
+
+        public StaEDIJsonStreamContext deepCopy() {
+            return new StaEDIJsonStreamContext(this);
+        }
+    }
+
+    static {
+        VERSION = Optional.ofNullable(StaEDIJacksonJsonParser.class.getResourceAsStream("/io/xlate/edi/internal/project.properties"))
+                .map(stream -> {
+                    try (InputStream source = stream) {
+                        Properties projectProperties = new Properties();
+                        projectProperties.load(source);
+                        return projectProperties;
+                    } catch (IOException e) {
+                        return null;
+                    }
+                })
+                .map(StaEDIJacksonJsonParser::toVersion)
+                .orElseGet(Version::unknownVersion);
+    }
+
+    static Version toVersion(Properties projectProperties) {
+        String[] version = projectProperties.getProperty("version").split("[.-]");
+
+        return new Version(
+                Integer.parseInt(version[0]),
+                Integer.parseInt(version[1]),
+                Integer.parseInt(version[2]),
+                version.length > 3 ? version[3] : null,
+                projectProperties.getProperty("groupId"),
+                projectProperties.getProperty("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 ace495b..85e18b8 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
@@ -19,7 +19,7 @@ import java.util.Map;
 
 import io.xlate.edi.stream.EDIStreamReader;
 
-final class StaEDIJakartaJsonParser extends StaEDIJsonParser
+final class StaEDIJakartaJsonParser extends StaEDIJsonParser<jakarta.json.JsonException>
         implements jakarta.json.stream.JsonParser, jakarta.json.stream.JsonLocation {
 
     static final jakarta.json.stream.JsonParser.Event[] eventMap = mapEvents(jakarta.json.stream.JsonParser.Event.class);
@@ -30,12 +30,12 @@ final class StaEDIJakartaJsonParser extends StaEDIJsonParser
     }
 
     @Override
-    protected RuntimeException newJsonException(String message, Throwable cause) {
+    protected jakarta.json.JsonException newJsonException(String message, Throwable cause) {
         return new jakarta.json.JsonException(message, cause);
     }
 
     @Override
-    protected RuntimeException newJsonParsingException(String message, Throwable cause) {
+    protected jakarta.json.JsonException newJsonParsingException(String message, Throwable cause) {
         return new jakarta.json.stream.JsonParsingException(message, cause, this);
     }
 
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 c3f7027..9b4a874 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
@@ -19,7 +19,7 @@ import java.util.Map;
 
 import io.xlate.edi.stream.EDIStreamReader;
 
-final class StaEDIJavaxJsonParser extends StaEDIJsonParser
+final class StaEDIJavaxJsonParser extends StaEDIJsonParser<javax.json.JsonException>
         implements javax.json.stream.JsonParser, javax.json.stream.JsonLocation {
 
     static final javax.json.stream.JsonParser.Event[] eventMap = mapEvents(javax.json.stream.JsonParser.Event.class);
@@ -30,12 +30,12 @@ final class StaEDIJavaxJsonParser extends StaEDIJsonParser
     }
 
     @Override
-    protected RuntimeException newJsonException(String message, Throwable cause) {
+    protected javax.json.JsonException newJsonException(String message, Throwable cause) {
         return new javax.json.JsonException(message, cause);
     }
 
     @Override
-    protected RuntimeException newJsonParsingException(String message, Throwable cause) {
+    protected javax.json.JsonException newJsonParsingException(String message, Throwable cause) {
         return new javax.json.stream.JsonParsingException(message, cause, this);
     }
 
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 f34b315..7901aad 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
@@ -15,17 +15,22 @@
  ******************************************************************************/
 package io.xlate.edi.internal.stream.json;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.text.DecimalFormat;
 import java.text.ParsePosition;
 import java.util.ArrayDeque;
+import java.util.Base64;
 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.Collectors;
 import java.util.stream.Stream;
 
 import io.xlate.edi.internal.stream.Configurable;
@@ -38,7 +43,7 @@ import io.xlate.edi.stream.EDIStreamException;
 import io.xlate.edi.stream.EDIStreamReader;
 import io.xlate.edi.stream.EDIValidationException;
 
-abstract class StaEDIJsonParser implements Configurable {
+abstract class StaEDIJsonParser<E extends Exception> implements Configurable {
 
     private static final Logger LOGGER = Logger.getLogger(StaEDIJsonParser.class.getName());
 
@@ -63,7 +68,9 @@ abstract class StaEDIJsonParser implements Configurable {
 
     Event currentEvent;
     String currentValue;
+    ByteArrayOutputStream currentBinaryValue = new ByteArrayOutputStream();
     BigDecimal currentNumber;
+    boolean closed = false;
 
     enum Event {
         /**
@@ -86,14 +93,6 @@ abstract class StaEDIJsonParser implements Configurable {
          * @see jakarta.json.stream.JsonParser.Event#VALUE_NUMBER
          */
         VALUE_NUMBER,
-        /**
-         * @see jakarta.json.stream.JsonParser.Event#VALUE_TRUE
-         */
-        VALUE_TRUE,
-        /**
-         * @see jakarta.json.stream.JsonParser.Event#VALUE_FALSE
-         */
-        VALUE_FALSE,
         /**
          * @see jakarta.json.stream.JsonParser.Event#VALUE_NULL
          */
@@ -123,24 +122,26 @@ abstract class StaEDIJsonParser implements Configurable {
 
     @SuppressWarnings("unchecked")
     static <E extends Enum<E>> E[] mapEvents(Class<E> eventType) {
-        ToIntFunction<E> ordinal = e -> Event.valueOf(e.name()).ordinal();
+        Map<String, Integer> ordinals = Stream.of(Event.values()).collect(Collectors.toMap(Enum::name, Enum::ordinal));
+        ToIntFunction<E> ordinal = e -> ordinals.get(e.name());
 
         return Stream.of(eventType.getEnumConstants())
+                .filter(c -> ordinals.containsKey(c.name()))
                 .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 E newJsonException(String message, Throwable cause);
 
-    protected abstract RuntimeException newJsonParsingException(String message, Throwable cause);
+    protected abstract E newJsonParsingException(String message, Throwable cause);
 
     @Override
     public Object getProperty(String name) {
         return properties.get(name);
     }
 
-    <T> T executeWithReader(EDIStreamReaderRunner<T> runner) {
+    <T> T executeWithReader(EDIStreamReaderRunner<T> runner) throws E {
         try {
             return runner.execute();
         } catch (EDIStreamException e) {
@@ -160,9 +161,14 @@ abstract class StaEDIJsonParser implements Configurable {
     void parseNumber(EDISimpleType elementType, String text) {
         if (elementType.getBase() == Base.NUMERIC) {
             final Integer scale = elementType.getScale();
-            final long unscaled = Long.parseLong(text);
 
-            this.currentNumber = BigDecimal.valueOf(unscaled, scale);
+            try {
+                final long unscaled = Long.parseLong(text);
+                this.currentNumber = BigDecimal.valueOf(unscaled, scale);
+            } catch (NumberFormatException e) {
+                final BigInteger unscaled = new BigInteger(text);
+                this.currentNumber = new BigDecimal(unscaled, scale);
+            }
         } else {
             decimalPosition.setIndex(0);
             decimalParser.setParseBigDecimal(true);
@@ -189,7 +195,24 @@ abstract class StaEDIJsonParser implements Configurable {
         enqueue(Event.START_ARRAY, null);
     }
 
-    void enqueueDataElement() {
+    void readBinaryValue() throws E {
+        try (InputStream binaryStream = ediReader.getBinaryData()) {
+            byte[] buffer = new byte[4096];
+            int bytesRead = -1;
+
+            while ((bytesRead = binaryStream.read(buffer)) > -1) {
+                currentBinaryValue.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            throw newJsonException(MSG_EXCEPTION, e);
+        }
+    }
+
+    boolean isNumber(EDISimpleType elementType) {
+        return elementType.getBase() == Base.DECIMAL || elementType.getBase() == Base.NUMERIC;
+    }
+
+    void enqueueDataElement(boolean binaryData) throws E {
         EDIReference referencedType = ediReader.getSchemaTypeReference();
         EDISimpleType elementType = null;
 
@@ -205,11 +228,16 @@ abstract class StaEDIJsonParser implements Configurable {
         }
 
         final Event dataEvent;
-        final String dataText = ediReader.getText();
+        final String dataText = ediReader.hasText() ? ediReader.getText() : "";
 
-        if (dataText.isEmpty() || elementType == null) {
+        if (elementType == null) {
+            dataEvent = Event.VALUE_STRING;
+        } else if (binaryData) {
+            readBinaryValue();
+            dataEvent = Event.VALUE_STRING;
+        } else if (dataText.isEmpty()) {
             dataEvent = this.emptyElementsNull ? Event.VALUE_NULL : Event.VALUE_STRING;
-        } else if (elementType.getBase() == Base.DECIMAL || elementType.getBase() == Base.NUMERIC) {
+        } else if (isNumber(elementType)) {
             Event numberEvent;
 
             try {
@@ -231,13 +259,18 @@ abstract class StaEDIJsonParser implements Configurable {
         }
     }
 
-    void enqueueEvent(EDIStreamEvent ediEvent) {
+    void enqueueEvent(EDIStreamEvent ediEvent) throws E {
         LOGGER.finer(() -> "Enqueue EDI event: " + ediEvent);
         currentNumber = null;
+        currentValue = null;
+        currentBinaryValue.reset();
 
         switch (ediEvent) {
         case ELEMENT_DATA:
-            enqueueDataElement();
+            enqueueDataElement(false);
+            break;
+        case ELEMENT_DATA_BINARY:
+            enqueueDataElement(true);
             break;
         case START_INTERCHANGE:
             enqueueStructureBegin("loop", "INTERCHANGE");
@@ -275,7 +308,7 @@ abstract class StaEDIJsonParser implements Configurable {
         }
     }
 
-    Event nextEvent() {
+    Event nextEvent() throws E {
         if (eventQueue.isEmpty()) {
             LOGGER.finer(() -> "eventQueue is empty, calling ediReader.next()");
             enqueueEvent(executeWithReader(ediReader::next));
@@ -307,11 +340,13 @@ abstract class StaEDIJsonParser implements Configurable {
         return ediReader.getLocation().getCharacterOffset();
     }
 
-    public void close() {
+    public void close() throws E {
         try {
             ediReader.close();
         } catch (IOException e) {
             throw newJsonException(MSG_EXCEPTION, e);
+        } finally {
+            closed = true;
         }
     }
 
@@ -350,7 +385,7 @@ abstract class StaEDIJsonParser implements Configurable {
      * @see jakarta.json.stream.JsonParser#hasNext()
      * @see javax.json.stream.JsonParser#hasNext()
      */
-    public boolean hasNext() {
+    public boolean hasNext() throws E {
         return !eventQueue.isEmpty() || executeWithReader(ediReader::hasNext);
     }
 
@@ -382,6 +417,9 @@ abstract class StaEDIJsonParser implements Configurable {
      */
     public String getString() {
         assertEventValueString();
+        if (currentBinaryValue.size() > 0) {
+            return Base64.getEncoder().encodeToString(currentBinaryValue.toByteArray());
+        }
         return this.currentValue;
     }
 
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java
index 3a170fe..09f615a 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java
@@ -543,7 +543,9 @@ public class ProxyEventHandler implements EventHandler {
 
     @Override
     public boolean binaryData(InputStream binaryStream) {
-        enqueueEvent(EDIStreamEvent.ELEMENT_DATA_BINARY, EDIStreamValidationError.NONE, "", null, location);
+        Validator validator = validator();
+        EDIReference typeReference = validator != null ? validator.getElementReference() : null;
+        enqueueEvent(EDIStreamEvent.ELEMENT_DATA_BINARY, EDIStreamValidationError.NONE, "", typeReference, location);
         setBinary(binaryStream);
         return true;
     }
diff --git a/src/main/java/io/xlate/edi/stream/EDIStreamReader.java b/src/main/java/io/xlate/edi/stream/EDIStreamReader.java
index 2f9aeb6..83bccde 100644
--- a/src/main/java/io/xlate/edi/stream/EDIStreamReader.java
+++ b/src/main/java/io/xlate/edi/stream/EDIStreamReader.java
@@ -435,11 +435,11 @@ public interface EDIStreamReader extends Closeable, EDIStreamConstants {
     void setBinaryDataLength(long length) throws EDIStreamException;
 
     /**
-     * Returns a ByteBuffer object containing the binary element data read in
-     * the previous data element. The limit of the buffer will be set to the
-     * length of the data.
+     * Returns an InputStream for reading the binary element data for the current
+     * element. The length of the input stream is determined by the value previously
+     * passed to {@link #setBinaryDataLength(long)}.
      *
-     * @return buffer containing binary data
+     * @return stream containing binary data
      * @throws IllegalStateException
      *             if the stream reader did not complete the scanning of a
      *             binary data element immediately preceding this call.
diff --git a/src/main/resources/io/xlate/edi/internal/project.properties b/src/main/resources/io/xlate/edi/internal/project.properties
new file mode 100644
index 0000000..951752f
--- /dev/null
+++ b/src/main/resources/io/xlate/edi/internal/project.properties
@@ -0,0 +1,3 @@
+groupId=${project.groupId}
+artifactId=${project.artifactId}
+version=${project.version}


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/stream/json/JsonParserInvoker.java b/src/test/java/io/xlate/edi/internal/stream/json/JsonParserInvoker.java
new file mode 100644
index 0000000..b0efaf2
--- /dev/null
+++ b/src/test/java/io/xlate/edi/internal/stream/json/JsonParserInvoker.java
@@ -0,0 +1,30 @@
+package io.xlate.edi.internal.stream.json;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+interface JsonParserInvoker {
+
+    @SuppressWarnings("unchecked")
+    default <T> T invoke(Object instance, String methodName, Class<T> returnType) throws Throwable {
+        Class<?> clazz = instance.getClass();
+
+        try {
+            Method method = Stream.of(clazz.getMethods(), clazz.getDeclaredMethods())
+                .flatMap(Arrays::stream)
+                .filter(m -> m.getName().equals(methodName))
+                .filter(m -> m.getParameterCount() == 0)
+                .findFirst()
+                .orElseThrow(() -> new NoSuchMethodException(methodName));
+
+            return (T) method.invoke(instance);
+        } catch (InvocationTargetException e) {
+            throw e.getTargetException();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/src/test/java/io/xlate/edi/internal/stream/json/StaEDIJacksonJsonParserTest.java b/src/test/java/io/xlate/edi/internal/stream/json/StaEDIJacksonJsonParserTest.java
new file mode 100644
index 0000000..a1677fa
--- /dev/null
+++ b/src/test/java/io/xlate/edi/internal/stream/json/StaEDIJacksonJsonParserTest.java
@@ -0,0 +1,292 @@
+package io.xlate.edi.internal.stream.json;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+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 java.math.BigDecimal;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonParser.NumberType;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.xlate.edi.stream.EDIInputFactory;
+import io.xlate.edi.test.StaEDIReaderTestBase;
+
+class StaEDIJacksonJsonParserTest extends StaEDIReaderTestBase implements JsonParserInvoker {
+
+    static byte[] TINY_X12 = ("ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+            + "IEA*1*508121953~").getBytes();
+
+    @Test
+    void testJacksonParserVersion() throws Throwable {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+        setupReader(TINY_X12, null);
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        Version version = jsonParser.version();
+        assertNotNull(version);
+        assertFalse(version.isUnknownVersion());
+    }
+
+    @Test
+    void testReadValueAsTree() throws Throwable {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+        setupReader("/x12/sample837-original.edi", "/x12/005010/837.xml");
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        assertFalse(jsonParser.isClosed());
+        jsonParser.setCodec(new ObjectMapper());
+        JsonNode tree = jsonParser.readValueAsTree();
+        assertNotNull(tree);
+        assertNull(jsonParser.nextToken());
+        jsonParser.close();
+        assertTrue(jsonParser.isClosed());
+
+        List<String> expected = Files.readAllLines(Paths.get(getClass().getResource("/x12/sample837-original.json").toURI()));
+        JSONAssert.assertEquals(String.join("", expected), tree.toString(), true);
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+        "                    1 , INT        , java.lang.Integer   , getIntValue",
+        "                   -1 , INT        , java.lang.Integer   , getIntValue",
+        "           9999999999 , LONG       , java.lang.Long      , getLongValue",
+        "          -9999999999 , LONG       , java.lang.Long      , getLongValue",
+        " 99999999999999999999 , BIG_INTEGER, java.math.BigInteger, getBigIntegerValue",
+        "-99999999999999999999 , BIG_INTEGER, java.math.BigInteger, getBigIntegerValue",
+    })
+    <T> void testNumericTypes(String inputValue, NumberType expectedType, Class<T> expectedClass, String directMethod) throws Throwable {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+
+        setupReader(("ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*AA*ReceiverDept*SenderDept*20200101*000000*1*X*005010~\n"
+                + "ST*000*0001*005010~\n"
+                + String.format("INT*%s~", inputValue)
+                + "FLT*1~"
+                + "SE*4*0001~\n"
+                + "GE*1*1~\n"
+                + "IEA*1*508121953~").getBytes(), "/x12/EDISchema000-numeric-types.xml");
+
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        JsonToken token;
+        boolean matched = false;
+
+        while ((token = jsonParser.nextToken()) != null) {
+            String pointer = jsonParser.getParsingContext().pathAsPointer().toString();
+            if ("/data/1/data/1/data/1/data/0".equals(pointer)) {
+                matched = true;
+                assertEquals(JsonToken.VALUE_NUMBER_INT, token);
+                assertEquals(expectedType, jsonParser.getNumberType());
+                Number actualValue = jsonParser.getNumberValue();
+                assertEquals(expectedClass, actualValue.getClass());
+                assertEquals(inputValue, String.valueOf(actualValue));
+                T directValue = invoke(jsonParser, directMethod, expectedClass);
+                assertEquals(actualValue, directValue);
+            }
+        }
+
+        assertTrue(matched);
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+        "1,                 1,   FLOAT,       java.lang.Float,      getFloatValue",
+        "34028234663852886, 38,  DOUBLE,      java.lang.Double,     getDoubleValue",
+        "18976931348623157, 308, BIG_DECIMAL, java.math.BigDecimal, getDecimalValue",
+    })
+    <T> void testDecimalTypesLargeValue(String prefix, int exp, NumberType expectedType, Class<T> expectedClass, String directMethod) throws Throwable {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+        BigDecimal inputValue = new BigDecimal(prefix + String.join("", Collections.nCopies(exp, "0")) + ".1");
+
+        setupReader(("ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*AA*ReceiverDept*SenderDept*20200101*000000*1*X*005010~\n"
+                + "ST*000*0001*005010~\n"
+                + "INT*1~"
+                + String.format("FLT*%s~", inputValue.toPlainString())
+                + "SE*4*0001~\n"
+                + "GE*1*1~\n"
+                + "IEA*1*508121953~").getBytes(), "/x12/EDISchema000-numeric-types.xml");
+
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        JsonToken token;
+        boolean matched = false;
+
+        while ((token = jsonParser.nextToken()) != null) {
+            String pointer = jsonParser.getParsingContext().pathAsPointer().toString();
+            if ("/data/1/data/1/data/2/data/0".equals(pointer)) {
+                matched = true;
+                assertEquals(JsonToken.VALUE_NUMBER_FLOAT, token);
+                assertEquals(expectedType, jsonParser.getNumberType());
+                Number actualValue = jsonParser.getNumberValue();
+                assertEquals(expectedClass, actualValue.getClass());
+                T directValue = invoke(jsonParser, directMethod, expectedClass);
+                assertEquals(actualValue, directValue);
+            }
+        }
+
+        assertTrue(matched);
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+        "1,     FLOAT,       java.lang.Float",
+        "128,   DOUBLE,      java.lang.Double",
+        "1024,  BIG_DECIMAL, java.math.BigDecimal",
+        "-1,    FLOAT,       java.lang.Float",
+        "-128,  DOUBLE,      java.lang.Double",
+        "-1024, BIG_DECIMAL, java.math.BigDecimal",
+    })
+    void testDecimalTypesLargeExponent(int exp, NumberType expectedType, Class<?> expectedClass) throws Exception {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+        BigDecimal inputValue;
+        if (exp > 0) {
+            inputValue = new BigDecimal("1" + String.join("", Collections.nCopies(exp, "0")) + ".1");
+        } else {
+            inputValue = new BigDecimal("0." + String.join("", Collections.nCopies(Math.abs(exp), "0")) + "1");
+        }
+
+        setupReader(("ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*AA*ReceiverDept*SenderDept*20200101*000000*1*X*005010~\n"
+                + "ST*000*0001*005010~\n"
+                + "INT*1~"
+                + String.format("FLT*%s~", inputValue.toPlainString())
+                + "SE*4*0001~\n"
+                + "GE*1*1~\n"
+                + "IEA*1*508121953~").getBytes(), "/x12/EDISchema000-numeric-types.xml");
+
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        JsonToken token;
+        boolean matched = false;
+
+        while ((token = jsonParser.nextToken()) != null) {
+            String pointer = jsonParser.getParsingContext().pathAsPointer().toString();
+            if ("/data/1/data/1/data/2/data".equals(pointer)) {
+                // Segment FLT
+                assertNull(jsonParser.getNumberType());
+                assertThrows(JsonParseException.class, () -> jsonParser.getNumberValue());
+            }
+
+            if ("/data/1/data/1/data/2/data/0".equals(pointer)) {
+                // Element FLT01
+                matched = true;
+                assertEquals(JsonToken.VALUE_NUMBER_FLOAT, token);
+                assertEquals(expectedType, jsonParser.getNumberType());
+                Number actualValue = jsonParser.getNumberValue();
+                assertEquals(expectedClass, actualValue.getClass());
+            }
+        }
+
+        assertTrue(matched);
+    }
+
+    @Test
+    void testPointersUnique() throws Exception {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+
+        setupReader(("ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*AA*ReceiverDept*SenderDept*20200101*000000*1*X*005010~\n"
+                + "ST*000*0001*005010~\n"
+                + "INT*1~"
+                + "FLT*1~"
+                + "SE*4*0001~\n"
+                + "GE*1*1~\n"
+                + "IEA*1*508121953~").getBytes(), "/x12/EDISchema000-numeric-types.xml");
+
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        JsonToken token;
+        Map<String, List<JsonToken>> pointers = new LinkedHashMap<>();
+
+        while ((token = jsonParser.nextToken()) != null) {
+            JsonToken tok = token;
+            String pointer = jsonParser.getParsingContext().pathAsPointer().toString();
+            List<JsonToken> tokens = pointers.computeIfAbsent(pointer, k -> new ArrayList<>());
+            assertFalse(tokens.contains(token), () -> "pointers already contains " + pointer + " for token " + tok);
+            assertTrue(tokens.size() <= 2, () -> "pointer " + pointer +" has too many tokens:" + tokens);
+            tokens.add(token);
+        }
+
+        assertFalse(pointers.isEmpty());
+    }
+
+    @Test
+    void testTextRetrieval() throws Exception {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+
+        setupReader(("ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*AA*ReceiverDept*SenderDept*20200101*000000*1*X*005010~\n"
+                + "ST*000*0001*005010~\n"
+                + "INT*1~"
+                + "FLT*1~"
+                + "SE*4*0001~\n"
+                + "GE*1*1~\n"
+                + "IEA*1*508121953~").getBytes(), "/x12/EDISchema000-numeric-types.xml");
+
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        boolean matched = false;
+
+        while (jsonParser.nextToken() != null) {
+            String pointer = jsonParser.getParsingContext().pathAsPointer().toString();
+
+            if ("/data/1/data/0/data/0".equals(pointer)) {
+                matched = true;
+                // Element GS01
+                assertEquals("AA", jsonParser.getText());
+                assertEquals(2, jsonParser.getTextLength());
+                assertArrayEquals(new char[] { 'A', 'A' }, Arrays.copyOfRange(
+                    jsonParser.getTextCharacters(),
+                    jsonParser.getTextOffset(),
+                    jsonParser.getTextLength()));
+            }
+        }
+
+        assertTrue(matched);
+    }
+
+    @Test
+    void testBinaryElementRetrieval() throws Exception {
+        ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
+        setupReader("/x12/simple_with_binary_segment.edi", "/x12/EDISchemaBinarySegment.xml");
+
+        JsonParser jsonParser = JsonParserFactory.createJsonParser(ediReader, JsonParser.class, ediReaderConfig);
+        int matched = 0;
+
+        while (jsonParser.nextToken() != null) {
+            String pointer = jsonParser.getParsingContext().pathAsPointer().toString();
+
+            if ("/data/1/data/1/data/1/data/1".equals(pointer)) {
+                matched++;
+                assertArrayEquals("1234567890123456789012345".getBytes(), jsonParser.getBinaryValue());
+            }
+            if ("/data/1/data/1/data/2/data/1".equals(pointer)) {
+                matched++;
+                assertArrayEquals("12345678901234567890\n1234".getBytes(), jsonParser.getBinaryValue());
+            }
+            if ("/data/1/data/1/data/3/data/1".equals(pointer)) {
+                matched++;
+                assertArrayEquals("1234567890\n1234567890\n12\n".getBytes(), jsonParser.getBinaryValue());
+            }
+        }
+
+        assertEquals(3, matched);
+    }
+}
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 09fa69d..d570c1c 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
@@ -2,7 +2,9 @@ package io.xlate.edi.internal.stream.json;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.ByteArrayOutputStream;
@@ -10,8 +12,6 @@ import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.nio.file.Files;
 import java.nio.file.Paths;
@@ -24,60 +24,18 @@ 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;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.junit.jupiter.params.provider.ValueSource;
 import org.skyscreamer.jsonassert.JSONAssert;
 
-import io.xlate.edi.schema.Schema;
-import io.xlate.edi.schema.SchemaFactory;
 import io.xlate.edi.stream.EDIInputFactory;
-import io.xlate.edi.stream.EDIStreamEvent;
 import io.xlate.edi.stream.EDIStreamReader;
 import io.xlate.edi.stream.EDIValidationException;
+import io.xlate.edi.test.StaEDIReaderTestBase;
 
-class StaEDIJsonParserTest {
-
-    Map<String, Object> ediReaderConfig;
-    EDIInputFactory factory;
-    EDIStreamReader ediReader;
-
-    @BeforeEach
-    void setup() throws Exception {
-        ediReaderConfig = new HashMap<>();
-        factory = EDIInputFactory.newFactory();
-    }
-
-    void setupReader(EDIInputFactory factory, String ediResource, String schemaResource) throws Exception {
-        InputStream stream = getClass().getResourceAsStream(ediResource);
-        ediReaderConfig.forEach(factory::setProperty);
-        ediReader = factory.createEDIStreamReader(stream);
-
-        if (schemaResource != null) {
-            SchemaFactory schemaFactory = SchemaFactory.newFactory();
-            Schema transactionSchema = schemaFactory.createSchema(getClass().getResource(schemaResource));
-            ediReader = factory.createFilteredReader(ediReader, (reader) -> {
-                if (reader.getEventType() == EDIStreamEvent.START_TRANSACTION) {
-                    reader.setTransactionSchema(transactionSchema);
-                }
-                return true;
-            });
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    <T> T invoke(Object instance, String methodName, Class<T> returnType) throws Throwable {
-        try {
-            Method method = instance.getClass().getMethod(methodName);
-            return (T) method.invoke(instance);
-        } catch (InvocationTargetException e) {
-            throw e.getTargetException();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
+class StaEDIJsonParserTest extends StaEDIReaderTestBase implements JsonParserInvoker {
 
     void copyParserToGenerator(EDIStreamReader ediReader, Object jsonParser, OutputStream buffer) throws Throwable {
         Map<String, Object> jsonConfig = new HashMap<>();
@@ -94,6 +52,9 @@ class StaEDIJsonParserTest {
             case "END_OBJECT":
                 jsonGenerator.writeEnd();
                 break;
+            case "FIELD_NAME": // Jackson only
+                assertTrue(invoke(jsonParser, "hasTextCharacters", Boolean.class));
+                // fall-through
             case "KEY_NAME":
                 jsonGenerator.writeKey(invoke(jsonParser, "getString", String.class));
                 break;
@@ -113,13 +74,22 @@ class StaEDIJsonParserTest {
                     jsonGenerator.write(invoke(jsonParser, "getBigDecimal", BigDecimal.class));
                 }
                 break;
+            case "VALUE_NUMBER_INT": // Jackson only
+                assertFalse(invoke(jsonParser, "hasTextCharacters", Boolean.class));
+                jsonGenerator.write(invoke(jsonParser, "getLongValue", Long.class));
+                break;
+            case "VALUE_NUMBER_FLOAT": // Jackson only
+                assertFalse(invoke(jsonParser, "hasTextCharacters", Boolean.class));
+                jsonGenerator.write(invoke(jsonParser, "getDecimalValue", BigDecimal.class));
+                break;
             case "VALUE_STRING":
                 jsonGenerator.write(invoke(jsonParser, "getString", String.class));
                 break;
             default:
-                fail("Unexpected event type");
+                fail("Unexpected event type: " + eventName);
             }
 
+            jsonGenerator.flush();
             Object location = invoke(jsonParser, "getLocation", Object.class);
             assertEquals(ediReader.getLocation().getCharacterOffset(), invoke(location, "getStreamOffset", Long.class));
             assertEquals(ediReader.getLocation().getLineNumber(), invoke(location, "getLineNumber", Long.class));
@@ -140,21 +110,27 @@ class StaEDIJsonParserTest {
 
     @ParameterizedTest
     @CsvSource({
-        "jakarta.json.stream.JsonParser, /x12/005010/837.xml,                            false, /x12/sample837-original.json",
-        "jakarta.json.stream.JsonParser, /x12/005010/837.xml,                            true,  /x12/sample837-original.json",
-        "jakarta.json.stream.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-nestHL.json",
-        "jakarta.json.stream.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837.xml,                            false, /x12/sample837-original.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837.xml,                            true,  /x12/sample837-original.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-nestHL.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original.json"
+        "jakarta.json.stream.JsonParser,        /x12/005010/837.xml,                            false, /x12/sample837-original.json",
+        "jakarta.json.stream.JsonParser,        /x12/005010/837.xml,                            true,  /x12/sample837-original.json",
+        "jakarta.json.stream.JsonParser,        /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-nestHL.json",
+        "jakarta.json.stream.JsonParser,        /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original.json",
+
+        "javax.json.stream.JsonParser,          /x12/005010/837.xml,                            false, /x12/sample837-original.json",
+        "javax.json.stream.JsonParser,          /x12/005010/837.xml,                            true,  /x12/sample837-original.json",
+        "javax.json.stream.JsonParser,          /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-nestHL.json",
+        "javax.json.stream.JsonParser,          /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original.json",
+
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837.xml,                            false, /x12/sample837-original.json",
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837.xml,                            true,  /x12/sample837-original.json",
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-nestHL.json",
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original.json"
     })
     void testNullElementsAsArray(Class<?> parserInterface, String schemaPath, boolean nestHL, String expectedResource) throws Throwable {
         ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, true);
         ediReaderConfig.put(EDIInputFactory.EDI_NEST_HIERARCHICAL_LOOPS, nestHL);
-        setupReader(factory, "/x12/sample837-original.edi", schemaPath);
+        setupReader("/x12/sample837-original.edi", schemaPath);
         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        Object jsonParser = factory.createJsonParser(ediReader, parserInterface);
+        Object jsonParser = ediInputFactory.createJsonParser(ediReader, parserInterface);
         copyParserToGenerator(ediReader, jsonParser, buffer);
         List<String> expected = Files.readAllLines(Paths.get(getClass().getResource(expectedResource).toURI()));
         System.out.println(buffer.toString());
@@ -163,19 +139,25 @@ class StaEDIJsonParserTest {
 
     @ParameterizedTest
     @CsvSource({
-        "jakarta.json.stream.JsonParser, /x12/005010/837.xml,                            false, /x12/sample837-original-object-elements.json",
-        "jakarta.json.stream.JsonParser, /x12/005010/837.xml,                            true,  /x12/sample837-original-object-elements.json",
-        "jakarta.json.stream.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-object-elements-nestHL.json",
-        "jakarta.json.stream.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original-object-elements.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837.xml,                            false, /x12/sample837-original-object-elements.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837.xml,                            true,  /x12/sample837-original-object-elements.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-object-elements-nestHL.json",
-        "javax.json.stream.JsonParser,   /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original-object-elements.json"
+        "jakarta.json.stream.JsonParser,        /x12/005010/837.xml,                            false, /x12/sample837-original-object-elements.json",
+        "jakarta.json.stream.JsonParser,        /x12/005010/837.xml,                            true,  /x12/sample837-original-object-elements.json",
+        "jakarta.json.stream.JsonParser,        /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-object-elements-nestHL.json",
+        "jakarta.json.stream.JsonParser,        /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original-object-elements.json",
+
+        "javax.json.stream.JsonParser,          /x12/005010/837.xml,                            false, /x12/sample837-original-object-elements.json",
+        "javax.json.stream.JsonParser,          /x12/005010/837.xml,                            true,  /x12/sample837-original-object-elements.json",
+        "javax.json.stream.JsonParser,          /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-object-elements-nestHL.json",
+        "javax.json.stream.JsonParser,          /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original-object-elements.json",
+
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837.xml,                            false, /x12/sample837-original-object-elements.json",
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837.xml,                            true,  /x12/sample837-original-object-elements.json",
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, true,  /x12/sample837-original-object-elements-nestHL.json",
+        "com.fasterxml.jackson.core.JsonParser, /x12/005010/837-hierarchical-level-enabled.xml, false, /x12/sample837-original-object-elements.json"
     })
     void testElementsAsObjects(Class<?> parserInterface, String schemaPath, boolean nestHL, String expectedResource) throws Throwable {
         ediReaderConfig.put(EDIInputFactory.JSON_OBJECT_ELEMENTS, true);
         ediReaderConfig.put(EDIInputFactory.EDI_NEST_HIERARCHICAL_LOOPS, nestHL);
-        setupReader(factory, "/x12/sample837-original.edi", schemaPath);
+        setupReader("/x12/sample837-original.edi", schemaPath);
         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
         Object jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
         copyParserToGenerator(ediReader, jsonParser, buffer);
@@ -205,7 +187,7 @@ class StaEDIJsonParserTest {
         ediReaderConfig.put(EDIInputFactory.JSON_OBJECT_ELEMENTS, objectElements);
         ediReaderConfig.put(EDIInputFactory.JSON_NULL_EMPTY_ELEMENTS, !objectElements);
 
-        setupReader(factory, "/x12/sample837-original.edi", schemaPath);
+        setupReader("/x12/sample837-original.edi", schemaPath);
         P jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
 
         String eventName = invoke(jsonParser, "next", Object.class).toString();
@@ -231,7 +213,7 @@ class StaEDIJsonParserTest {
         "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");
+        setupReader("/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());
@@ -244,7 +226,7 @@ class StaEDIJsonParserTest {
                         javax.json.stream.JsonParser.class
             })
     void testNumbersAllComparable(Class<?> parserInterface) throws Throwable {
-        setupReader(factory, "/x12/simple810.edi", "/x12/EDISchema810.xml");
+        setupReader("/x12/simple810.edi", "/x12/EDISchema810.xml");
         Object jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
 
         while (invoke(jsonParser, "hasNext", Boolean.class)) {
@@ -279,10 +261,11 @@ class StaEDIJsonParserTest {
     @ValueSource(
             classes = {
                         jakarta.json.stream.JsonParser.class,
-                        javax.json.stream.JsonParser.class
+                        javax.json.stream.JsonParser.class,
+                        com.fasterxml.jackson.core.JsonParser.class
             })
     void testTextStatesNotIllegal(Class<?> parserInterface) throws Throwable {
-        setupReader(factory, "/x12/sample837-original.edi", "/x12/005010/837.xml");
+        setupReader("/x12/sample837-original.edi", "/x12/005010/837.xml");
         Object jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
 
         while (invoke(jsonParser, "hasNext", Boolean.class)) {
@@ -290,8 +273,11 @@ class StaEDIJsonParserTest {
 
             switch (eventName) {
             case "KEY_NAME":
+            case "FIELD_NAME": // Jackson only
             case "VALUE_STRING":
             case "VALUE_NUMBER":
+            case "VALUE_NUMBER_INT": // Jackson only
+            case "VALUE_NUMBER_FLOAT": // Jackson only
                 assertDoesNotThrow(() -> invoke(jsonParser, "getString", String.class));
                 break;
             default:
@@ -307,10 +293,11 @@ class StaEDIJsonParserTest {
     @ValueSource(
             classes = {
                         jakarta.json.stream.JsonParser.class,
-                        javax.json.stream.JsonParser.class
+                        javax.json.stream.JsonParser.class,
+                        com.fasterxml.jackson.core.JsonParser.class
             })
     void testTextStatesIllegalForNumber(Class<?> parserInterface) throws Throwable {
-        setupReader(factory, "/x12/sample837-original.edi", "/x12/005010/837.xml");
+        setupReader("/x12/sample837-original.edi", "/x12/005010/837.xml");
         Object jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
 
         while (invoke(jsonParser, "hasNext", Boolean.class)) {
@@ -318,6 +305,8 @@ class StaEDIJsonParserTest {
 
             switch (eventName) {
             case "VALUE_NUMBER":
+            case "VALUE_NUMBER_INT": // Jackson only
+            case "VALUE_NUMBER_FLOAT": // Jackson only
                 assertDoesNotThrow(() -> invoke(jsonParser, "getInt", Integer.class));
                 break;
             default:
@@ -333,10 +322,11 @@ class StaEDIJsonParserTest {
     @ValueSource(
             classes = {
                         jakarta.json.stream.JsonParser.class,
-                        javax.json.stream.JsonParser.class
+                        javax.json.stream.JsonParser.class,
+                        com.fasterxml.jackson.core.JsonParser.class
             })
     void testInvalidNumberThrowsException(Class<?> parserInterface) throws Throwable {
-        setupReader(factory, "/x12/invalid997_min.edi", null);
+        setupReader("/x12/invalid997_min.edi", null);
         Object jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
         List<Exception> errors = new ArrayList<>();
 
@@ -365,13 +355,14 @@ class StaEDIJsonParserTest {
 
     @ParameterizedTest
     @CsvSource({
-        "jakarta.json.stream.JsonParser,jakarta.json.JsonException",
-        "javax.json.stream.JsonParser,javax.json.JsonException"
+        "jakarta.json.stream.JsonParser       , jakarta.json.JsonException",
+        "javax.json.stream.JsonParser         , javax.json.JsonException",
+        "com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.core.JsonParseException"
     })
     void testIOExceptionThrowsCorrectJsonException(String parserName, String exceptionName) throws Throwable {
         InputStream stream = getClass().getResourceAsStream("/x12/sample837-original.edi");
-        ediReaderConfig.forEach(factory::setProperty);
-        ediReader = factory.createEDIStreamReader(new FilterInputStream(stream) {
+        ediReaderConfig.forEach(ediInputFactory::setProperty);
+        ediReader = ediInputFactory.createEDIStreamReader(new FilterInputStream(stream) {
             @Override
             public int read() throws IOException {
                 if (ediReader.getLocation().getCharacterOffset() > 50) {
@@ -399,4 +390,22 @@ class StaEDIJsonParserTest {
         assertEquals(1, errors.size());
         assertEquals(exceptionType, errors.get(0).getClass());
     }
+
+    @ParameterizedTest
+    @ValueSource(
+            classes = {
+                        jakarta.json.stream.JsonParser.class,
+                        javax.json.stream.JsonParser.class,
+                        com.fasterxml.jackson.core.JsonParser.class
+            })
+    <T> void testBinaryDataReadAsEncodedString(Class<T> parserInterface) throws Throwable {
+        ediReaderConfig.forEach(ediInputFactory::setProperty);
+        setupReader("/x12/simple_with_binary_segment.edi", "/x12/EDISchemaBinarySegment.xml");
+        T jsonParser = JsonParserFactory.createJsonParser(ediReader, parserInterface, ediReaderConfig);
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        copyParserToGenerator(ediReader, jsonParser, buffer);
+        List<String> expected = Files.readAllLines(Paths.get(getClass().getResource("/x12/simple_with_binary_segment.json").toURI()));
+        System.out.println(buffer.toString());
+        JSONAssert.assertEquals(String.join("", expected), buffer.toString(), true);
+    }
 }
diff --git a/src/test/java/io/xlate/edi/test/StaEDIReaderTestBase.java b/src/test/java/io/xlate/edi/test/StaEDIReaderTestBase.java
new file mode 100644
index 0000000..588995b
--- /dev/null
+++ b/src/test/java/io/xlate/edi/test/StaEDIReaderTestBase.java
@@ -0,0 +1,52 @@
+package io.xlate.edi.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+
+import io.xlate.edi.schema.Schema;
+import io.xlate.edi.schema.SchemaFactory;
+import io.xlate.edi.stream.EDIInputFactory;
+import io.xlate.edi.stream.EDIStreamEvent;
+import io.xlate.edi.stream.EDIStreamReader;
+
+public class StaEDIReaderTestBase {
+
+    protected Map<String, Object> ediReaderConfig;
+    protected EDIInputFactory ediInputFactory;
+    protected EDIStreamReader ediReader;
+
+    @BeforeEach
+    void setupFactory() throws Exception {
+        ediReaderConfig = new HashMap<>();
+        ediInputFactory = EDIInputFactory.newFactory();
+    }
+
+    protected void setupReader(InputStream ediStream, String schemaResource) throws Exception {
+        ediReaderConfig.forEach(ediInputFactory::setProperty);
+        ediReader = ediInputFactory.createEDIStreamReader(ediStream);
+
+        if (schemaResource != null) {
+            SchemaFactory schemaFactory = SchemaFactory.newFactory();
+            Schema transactionSchema = schemaFactory.createSchema(getClass().getResource(schemaResource));
+            ediReader = ediInputFactory.createFilteredReader(ediReader, (reader) -> {
+                if (reader.getEventType() == EDIStreamEvent.START_TRANSACTION) {
+                    reader.setTransactionSchema(transactionSchema);
+                }
+                return true;
+            });
+        }
+    }
+
+    protected void setupReader(String ediResource, String schemaResource) throws Exception {
+        setupReader(getClass().getResourceAsStream(ediResource), schemaResource);
+    }
+
+    protected void setupReader(byte[] ediResource, String schemaResource) throws Exception {
+        setupReader(new ByteArrayInputStream(ediResource), schemaResource);
+    }
+
+}
diff --git a/src/test/resources/x12/EDISchema000-numeric-types.xml b/src/test/resources/x12/EDISchema000-numeric-types.xml
new file mode 100644
index 0000000..2245d36
--- /dev/null
+++ b/src/test/resources/x12/EDISchema000-numeric-types.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  Copyright 2017, 2019 xlate.io LLC, http://www.xlate.io
+
+  Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  use this file except in compliance with the License.  You may obtain a copy
+  of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  License for the specific language governing permissions and limitations under
+  the License.
+-->
+<schema xmlns="http://xlate.io/EDISchema/v4">
+  <transaction>
+    <sequence>
+      <segment type="INT" minOccurs="1" />
+      <segment type="FLT" minOccurs="1" />
+    </sequence>
+  </transaction>
+
+  <elementType name="E1000" code="1000" base="numeric" maxLength="10000" />
+  <elementType name="E2000" code="2000" base="decimal" maxLength="10000" />
+
+  <segmentType name="INT">
+    <sequence>
+      <element type="E1000" minOccurs="1" />
+    </sequence>
+  </segmentType>
+
+  <segmentType name="FLT">
+    <sequence>
+      <element type="E2000" minOccurs="1" />
+    </sequence>
+  </segmentType>
+</schema>
diff --git a/src/test/resources/x12/simple_with_binary_segment.json b/src/test/resources/x12/simple_with_binary_segment.json
new file mode 100644
index 0000000..639d5fe
--- /dev/null
+++ b/src/test/resources/x12/simple_with_binary_segment.json
@@ -0,0 +1,110 @@
+{
+    "name": "INTERCHANGE",
+    "type": "loop",
+    "data": [
+        {
+            "name": "ISA",
+            "type": "segment",
+            "data": [
+                "00",
+                "          ",
+                "00",
+                "          ",
+                "ZZ",
+                "ReceiverID     ",
+                "ZZ",
+                "Sender         ",
+                "050812",
+                "1953",
+                "^",
+                "00501",
+                508121953,
+                "0",
+                "P",
+                ":"
+            ]
+        },
+        {
+            "name": "GROUP",
+            "type": "loop",
+            "data": [
+                {
+                    "name": "GS",
+                    "type": "segment",
+                    "data": [
+                        "FA",
+                        "ReceiverDept",
+                        "SenderDept",
+                        "20190922",
+                        "155401",
+                        1,
+                        "X",
+                        "005010"
+                    ]
+                },
+                {
+                    "name": "TRANSACTION",
+                    "type": "loop",
+                    "data": [
+                        {
+                            "name": "ST",
+                            "type": "segment",
+                            "data": [
+                                "000",
+                                "0001"
+                            ]
+                        },
+                        {
+                            "name": "BIN",
+                            "type": "segment",
+                            "data": [
+                                25,
+                                "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NQ=="
+                            ]
+                        },
+                        {
+                            "name": "BIN",
+                            "type": "segment",
+                            "data": [
+                                25,
+                                "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAKMTIzNA=="
+                            ]
+                        },
+                        {
+                            "name": "BIN",
+                            "type": "segment",
+                            "data": [
+                                25,
+                                "MTIzNDU2Nzg5MAoxMjM0NTY3ODkwCjEyCg=="
+                            ]
+                        },
+                        {
+                            "name": "SE",
+                            "type": "segment",
+                            "data": [
+                                5,
+                                "0001"
+                            ]
+                        }
+                    ]
+                },
+                {
+                    "name": "GE",
+                    "type": "segment",
+                    "data": [
+                        1,
+                        1
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "IEA",
+            "type": "segment",
+            "data": [
+                1,
+                508121953
+            ]
+        }
+    ]
+}