Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON output no longer quotes all values #1298

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@

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;
Expand Down Expand Up @@ -1492,5 +1494,51 @@ public void testJavaAPICompileSource2() throws IOException {
}
}

@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"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
stevedlawrence marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"))
}
}

}