This is an automated email from the ASF dual-hosted git repository.

slawrence pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new 31d550b20 JSON output no longer quotes all values
31d550b20 is described below

commit 31d550b207ad82b90674431336d18f788f6e27eb
Author: aparker <[email protected]>
AuthorDate: Fri Sep 13 17:03:24 2024 -0400

    JSON output no longer quotes all values
    
    JSONInfosetInputter and Outputter were treating all values as strings.
    This meant everyhing was quoted and was messier for the user to read.
    In addition, not all json comes from dfdl, so it makes sense to support 
numbers.
    
    The JSONInfosetInputter's getSimpleText function also throws a
    NonTextFoundInSimpleContentException if a node in a nonscalar value, since
    finding a nonscalar (such as an object or array) when your expecting a 
simple value
    means the infoset does not match.
    
     DAFFODIL-2362
    
    delete me
---
 .../org/apache/daffodil/example/TestJavaAPI.java   | 48 ++++++++++++++++++++
 .../runtime1/infoset/JsonInfosetInputter.scala     | 15 ++++--
 .../runtime1/infoset/JsonInfosetOutputter.scala    | 32 +++++++++++--
 .../org/apache/daffodil/example/TestScalaAPI.scala | 53 ++++++++++++++++++++++
 4 files changed, 141 insertions(+), 7 deletions(-)

diff --git 
a/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java
index 0942ef79b..6ef3e303a 100644
--- a/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java
+++ b/daffodil-japi/src/test/java/org/apache/daffodil/example/TestJavaAPI.java
@@ -50,6 +50,8 @@ import org.junit.Test;
 
 import org.apache.daffodil.japi.infoset.JDOMInfosetInputter;
 import org.apache.daffodil.japi.infoset.JDOMInfosetOutputter;
+import org.apache.daffodil.japi.infoset.JsonInfosetOutputter;
+import org.apache.daffodil.japi.infoset.JsonInfosetInputter;
 import org.apache.daffodil.japi.io.InputSourceDataInputStream;
 import org.xml.sax.SAXNotRecognizedException;
 import org.xml.sax.SAXNotSupportedException;
@@ -1492,5 +1494,51 @@ public class TestJavaAPI {
         }
     }
 
+    @Test
+    public void testJavaAPIJson1() throws IOException, ClassNotFoundException {
+        org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+        java.io.File schemaFile = getResource("/test/japi/mySchema1.dfdl.xsd");
+        ProcessorFactory pf = c.compileFile(schemaFile);
+        DataProcessor dp = pf.onPath("/");
+
+        java.io.File file = getResource("/test/japi/myData.dat");
+        java.io.FileInputStream fis = new java.io.FileInputStream(file);
+        InputSourceDataInputStream dis = new InputSourceDataInputStream(fis);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        JsonInfosetOutputter outputter = new JsonInfosetOutputter(bos, false);
+        ParseResult res = dp.parse(dis, outputter);
+        assertFalse(res.isError());
+
+        java.io.ByteArrayInputStream input = new 
java.io.ByteArrayInputStream(bos.toByteArray());
+        JsonInfosetInputter inputter = new JsonInfosetInputter(input);
+        java.io.ByteArrayOutputStream bos2 = new 
java.io.ByteArrayOutputStream();
+        java.nio.channels.WritableByteChannel wbc = 
java.nio.channels.Channels.newChannel(bos2);
+        UnparseResult res2 = dp.unparse(inputter, wbc);
+        assertFalse(res2.isError());
+        assertEquals("42", bos2.toString());
+    }
+
+    @Test
+    public void testJavaAPIJson2() throws IOException, ClassNotFoundException {
+        org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+        java.io.File schemaFile = getResource("/test/japi/mySchema1.dfdl.xsd");
+        ProcessorFactory pf = c.compileFile(schemaFile);
+        DataProcessor dp = pf.onPath("/");
+
+        // e2 should be a simple type
+        String badJsonInfoset = "{\"e1\": {\"e2\": {\"unexpected\": 
\"object\"}}}";
+
+        java.io.ByteArrayInputStream input = new 
java.io.ByteArrayInputStream(badJsonInfoset.getBytes("UTF-8"));
+        JsonInfosetInputter inputter = new JsonInfosetInputter(input);
+        java.io.ByteArrayOutputStream bos2 = new 
java.io.ByteArrayOutputStream();
+        java.nio.channels.WritableByteChannel wbc = 
java.nio.channels.Channels.newChannel(bos2);
+        UnparseResult res = dp.unparse(inputter, wbc);
+        assertTrue(res.isError());
+        java.util.List<Diagnostic> diags = res.getDiagnostics();
+        assertEquals(1, diags.size());
+        assertTrue(diags.get(0).toString().contains("Illegal content for 
simple element"));
+        assertTrue(diags.get(0).toString().contains("Unexpected array or 
object"));
+        assertTrue(diags.get(0).toString().contains("e2"));
+    }
 
 }
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala
 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala
index f305ad2a4..55c7d1be2 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetInputter.scala
@@ -85,7 +85,9 @@ class JsonInfosetInputter(input: java.io.InputStream) extends 
InfosetInputter {
       jsp.getCurrentToken() match {
         case JsonToken.START_OBJECT => if (objectDepth == 1) StartDocument 
else StartElement
         case JsonToken.END_OBJECT => EndElement
-        case JsonToken.VALUE_STRING | JsonToken.VALUE_NULL => {
+        case JsonToken.VALUE_STRING | JsonToken.VALUE_NUMBER_INT |
+            JsonToken.VALUE_NUMBER_FLOAT | JsonToken.VALUE_TRUE | 
JsonToken.VALUE_FALSE |
+            JsonToken.VALUE_NULL => {
           // we don't want to start faking element end yet, but signify that
           // after a call to next(), we will want to fake it
           nextEventShouldBeFakeEnd = true
@@ -118,10 +120,15 @@ class JsonInfosetInputter(input: java.io.InputStream) 
extends InfosetInputter {
     primType: NodeInfo.Kind,
     runtimeProperties: java.util.Map[String, String]
   ): String = {
-    if (jsp.getCurrentToken() == JsonToken.VALUE_NULL) {
+    if (!jsp.getCurrentToken().isScalarValue()) {
+      throw new NonTextFoundInSimpleContentException(
+        "Unexpected array or object '" + getLocalName + "' on line " + jsp
+          .getTokenLocation()
+          .getLineNr()
+      )
+    } else if (jsp.getCurrentToken() == JsonToken.VALUE_NULL) {
       null
     } else {
-      Assert.invariant(jsp.getCurrentToken() == JsonToken.VALUE_STRING)
       // this handles unescaping any escaped characters
       jsp.getText()
     }
@@ -172,7 +179,7 @@ class JsonInfosetInputter(input: java.io.InputStream) 
extends InfosetInputter {
             objectDepth -= 1; exitNow = true
 
           // start of a simple type or null
-          case JsonToken.VALUE_STRING | JsonToken.VALUE_NULL => exitNow = true
+          case token if token.isScalarValue() => exitNow = true
 
           // skip field names, jackson makes these available via
           // getCurrentName(), except for array elements
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala
 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala
index 9b325c628..878b633de 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/infoset/JsonInfosetOutputter.scala
@@ -112,14 +112,40 @@ class JsonInfosetOutputter private (writer: 
java.io.BufferedWriter, pretty: Bool
         } else {
           simple.getText
         }
-      writer.write('"')
-      writer.write(text)
-      writer.write('"')
+      if (needsQuote(simple)) {
+        writer.write('"')
+        writer.write(text)
+        writer.write('"')
+      } else {
+        writer.write(text)
+      }
     } else {
       writer.write("null")
     }
   }
 
+  private def needsQuote(simple: InfosetSimpleElement): Boolean = {
+    simple.metadata.dfdlType match {
+      case DFDLPrimType.String => true
+      case DFDLPrimType.HexBinary => true
+      case DFDLPrimType.AnyURI => true
+      case DFDLPrimType.DateTime => true
+      case DFDLPrimType.Date => true
+      case DFDLPrimType.Time => true
+
+      // json does not support inf/nan double/float so they must be quoted
+      case DFDLPrimType.Double => {
+        val d = simple.getDouble
+        d.isInfinite || d.isNaN
+      }
+      case DFDLPrimType.Float => {
+        val f = simple.getFloat
+        f.isInfinite || f.isNaN
+      }
+      case _ => false
+    }
+  }
+
   override def endSimple(se: InfosetSimpleElement): Unit = {
     // nothing to do
   }
diff --git 
a/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala
index 2456c474e..71692a924 100644
--- 
a/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala
+++ 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/TestScalaAPI.scala
@@ -42,6 +42,8 @@ import org.apache.daffodil.sapi.InvalidUsageException
 import org.apache.daffodil.sapi.ParseResult
 import org.apache.daffodil.sapi.SAXErrorHandlerForSAPITest
 import org.apache.daffodil.sapi.ValidationMode
+import org.apache.daffodil.sapi.infoset.JsonInfosetInputter
+import org.apache.daffodil.sapi.infoset.JsonInfosetOutputter
 import org.apache.daffodil.sapi.infoset.ScalaXMLInfosetInputter
 import org.apache.daffodil.sapi.infoset.ScalaXMLInfosetOutputter
 import org.apache.daffodil.sapi.infoset.XMLTextEscapeStyle
@@ -1454,4 +1456,55 @@ class TestScalaAPI {
       if (tempFile.exists) tempFile.delete()
     }
   }
+
+  @Test
+  def testScalaAPIJson1(): Unit = {
+    val c = Daffodil.compiler()
+    val name = "/test/sapi/mySchema1.dfdl.xsd"
+    val pf = c.compileResource(name)
+    val dp = pf.onPath("/")
+
+    val file = getResource("/test/sapi/myData.dat")
+    val fis = new java.io.FileInputStream(file)
+    val bos = new ByteArrayOutputStream()
+    using(new InputSourceDataInputStream(fis)) { input =>
+      val outputter = new JsonInfosetOutputter(bos, pretty = false)
+      val res = dp.parse(input, outputter)
+      assertFalse(res.isError())
+    }
+
+    using(new ByteArrayInputStream(bos.toByteArray())) { input =>
+      val bos = new java.io.ByteArrayOutputStream()
+      val wbc = java.nio.channels.Channels.newChannel(bos)
+      val inputter = new JsonInfosetInputter(input)
+      val res = dp.unparse(inputter, wbc)
+      assertFalse(res.isError())
+      assertEquals("42", bos.toString())
+    }
+  }
+
+  @Test
+  def testScalaAPIJson2(): Unit = {
+    val c = Daffodil.compiler()
+    val name = "/test/sapi/mySchema1.dfdl.xsd"
+    val pf = c.compileResource(name)
+    val dp = pf.onPath("/")
+
+    // e2 should be a simple type
+    val badJsonInfoset = """{"e1": {"e2": {"unexpected": "object"}}}"""
+
+    using(new ByteArrayInputStream(badJsonInfoset.getBytes("UTF-8"))) { input 
=>
+      val bos = new java.io.ByteArrayOutputStream()
+      val wbc = java.nio.channels.Channels.newChannel(bos)
+      val inputter = new JsonInfosetInputter(input)
+      val res = dp.unparse(inputter, wbc)
+      assertTrue(res.isError())
+      val diags = res.getDiagnostics
+      assertEquals(1, diags.length)
+      assertTrue(diags(0).toString.contains("Illegal content for simple 
element"))
+      assertTrue(diags(0).toString.contains("Unexpected array or object"))
+      assertTrue(diags(0).toString.contains("e2"))
+    }
+  }
+
 }

Reply via email to