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"))
+ }
+ }
+
}