Skip to content

Commit 527094d

Browse files
authored
Merge pull request #4387 from evolvedbinary/bugfix/json-number
Fix issues with range, precision, and exponent with fn:xml-to-json
2 parents 65d3d5e + 1f45120 commit 527094d

File tree

4 files changed

+140
-73
lines changed

4 files changed

+140
-73
lines changed

exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,12 @@ public class FnModule extends AbstractInternalModule {
240240
new FunctionDef(FunEnvironment.signature[1], FunEnvironment.class),
241241
new FunctionDef(ParsingFunctions.signatures[0], ParsingFunctions.class),
242242
new FunctionDef(ParsingFunctions.signatures[1], ParsingFunctions.class),
243-
new FunctionDef(JSON.signatures[0], JSON.class),
244-
new FunctionDef(JSON.signatures[1], JSON.class),
245-
new FunctionDef(JSON.signatures[2], JSON.class),
246-
new FunctionDef(JSON.signatures[3], JSON.class),
247-
new FunctionDef(JSON.signatures[4], JSON.class),
248-
new FunctionDef(JSON.signatures[5], JSON.class),
243+
new FunctionDef(JSON.FS_PARSE_JSON[0], JSON.class),
244+
new FunctionDef(JSON.FS_PARSE_JSON[1], JSON.class),
245+
new FunctionDef(JSON.FS_JSON_DOC[0], JSON.class),
246+
new FunctionDef(JSON.FS_JSON_DOC[1], JSON.class),
247+
new FunctionDef(JSON.FS_JSON_TO_XML[0], JSON.class),
248+
new FunctionDef(JSON.FS_JSON_TO_XML[1], JSON.class),
249249
new FunctionDef(LoadXQueryModule.LOAD_XQUERY_MODULE_1, LoadXQueryModule.class),
250250
new FunctionDef(LoadXQueryModule.LOAD_XQUERY_MODULE_2, LoadXQueryModule.class),
251251
new FunctionDef(FunSort.signatures[0], FunSort.class),

exist-core/src/main/java/org/exist/xquery/functions/fn/FunXmlToJson.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.io.IOException;
3535
import java.io.StringWriter;
3636
import java.io.Writer;
37+
import java.math.BigDecimal;
3738
import java.util.ArrayList;
3839

3940
import static org.exist.xquery.FunctionDSL.*;
@@ -43,7 +44,7 @@
4344
*/
4445
public class FunXmlToJson extends BasicFunction {
4546

46-
private final static Logger logger = LogManager.getLogger();
47+
private static final Logger logger = LogManager.getLogger();
4748

4849
private static final String FS_XML_TO_JSON_NAME = "xml-to-json";
4950
private static final FunctionParameterSequenceType FS_XML_TO_JSON_OPT_PARAM_NODE = optParam("node", Type.NODE, "The input node");
@@ -179,7 +180,7 @@ private void nodeValueToJson(final NodeValue nodeValue, final Writer writer) thr
179180
break;
180181
case "number":
181182
try{
182-
final double tempDouble = Double.parseDouble(tempString);
183+
final BigDecimal tempDouble = new BigDecimal(tempString);
183184
jsonGenerator.writeNumber(tempDouble);
184185
} catch (NumberFormatException ex){
185186
throw new XPathException(ErrorCodes.FOJS0006, "Cannot convert '" + tempString + "' to a number.");

exist-core/src/main/java/org/exist/xquery/functions/fn/JSON.java

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -39,69 +39,68 @@
3939
import java.io.IOException;
4040
import java.io.InputStream;
4141

42+
import static org.exist.xquery.FunctionDSL.*;
43+
import static org.exist.xquery.functions.fn.FnModule.functionSignatures;
44+
4245
/**
4346
* Functions related to JSON parsing.
4447
*
4548
* @author Wolf
4649
*/
4750
public class JSON extends BasicFunction {
4851

49-
public static final FunctionSignature[] signatures = {
50-
new FunctionSignature(
51-
new QName("parse-json", Function.BUILTIN_FUNCTION_NS),
52-
"Parses a string supplied in the form of a JSON text, returning the results typically in the form of a map or array.",
53-
new SequenceType[]{
54-
new FunctionParameterSequenceType("json-text", Type.STRING, Cardinality.ZERO_OR_ONE, "JSON string")
55-
},
56-
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE, "The parsed data, typically a map, array or atomic value")
57-
),
58-
new FunctionSignature(
59-
new QName("parse-json", Function.BUILTIN_FUNCTION_NS),
60-
"Parses a string supplied in the form of a JSON text, returning the results typically in the form of a map or array.",
61-
new SequenceType[]{
62-
new FunctionParameterSequenceType("json-text", Type.STRING, Cardinality.ZERO_OR_ONE, "JSON string"),
63-
new FunctionParameterSequenceType("options", Type.MAP, Cardinality.EXACTLY_ONE, "Parsing options")
64-
},
65-
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE, "The parsed data, typically a map, array or atomic value")
66-
),
67-
new FunctionSignature(
68-
new QName("json-doc", Function.BUILTIN_FUNCTION_NS),
69-
"Reads an external (or database) resource containing JSON, and returns the results of parsing the resource as JSON. An URL parameter " +
70-
"without scheme or scheme 'xmldb:' is considered to point to a database resource.",
71-
new SequenceType[]{
72-
new FunctionParameterSequenceType("href", Type.STRING, Cardinality.ZERO_OR_ONE, "URL pointing to a JSON resource")
73-
},
74-
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE, "The parsed data, typically a map, array or atomic value")
75-
),
76-
new FunctionSignature(
77-
new QName("json-doc", Function.BUILTIN_FUNCTION_NS),
78-
"Reads an external (or database) resource containing JSON, and returns the results of parsing the resource as JSON. An URL parameter " +
79-
"without scheme or scheme 'xmldb:' is considered to point to a database resource.",
80-
new SequenceType[]{
81-
new FunctionParameterSequenceType("href", Type.STRING, Cardinality.ZERO_OR_ONE, "URL pointing to a JSON resource"),
82-
new FunctionParameterSequenceType("options", Type.MAP, Cardinality.EXACTLY_ONE, "Parsing options")
83-
},
84-
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE, "The parsed data, typically a map, array or atomic value")
85-
),
86-
new FunctionSignature(
87-
new QName("json-to-xml", Function.BUILTIN_FUNCTION_NS),
88-
"Parses a string supplied in the form of a JSON text, returning the results in the form of an XML document node.",
89-
new SequenceType[]{
90-
new FunctionParameterSequenceType("json-text", Type.STRING, Cardinality.ZERO_OR_ONE, "JSON text as defined in [RFC 7159]. The function parses this string to return an XDM value"),
91-
},
92-
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE, "The parsed data as XML")
93-
),
52+
private static final FunctionParameterSequenceType FS_PARAM_JSON_TEXT = optParam("json-text", Type.STRING, "JSON text as defined in [RFC 7159]. The function parses this string to return an XDM value");
53+
private static final FunctionParameterSequenceType FS_PARAM_HREF = optParam("href", Type.STRING,"URL pointing to a JSON resource");
54+
private static final FunctionParameterSequenceType FS_PARAM_OPTIONS = param("options", Type.MAP, "Parsing options");
55+
56+
private static final String FS_PARSE_JSON_NAME = "parse-json";
57+
static final FunctionSignature[] FS_PARSE_JSON = functionSignatures(
58+
FS_PARSE_JSON_NAME,
59+
"Parses a string supplied in the form of a JSON text, returning the results typically in the form of a map or array.",
60+
returnsOpt(Type.ITEM, "The parsed data, typically a map, array or atomic value"),
61+
arities(
62+
arity(
63+
FS_PARAM_JSON_TEXT
64+
),
65+
arity(
66+
FS_PARAM_JSON_TEXT,
67+
param("options", Type.MAP, "Parsing options")
68+
)
69+
)
70+
);
9471

95-
new FunctionSignature(
96-
new QName("json-to-xml", Function.BUILTIN_FUNCTION_NS),
97-
"Parses a string supplied in the form of a JSON text, returning the results in the form of an XML document node.",
98-
new SequenceType[]{
99-
new FunctionParameterSequenceType("json-text", Type.STRING, Cardinality.ZERO_OR_ONE, "JSON text as defined in [RFC 7159]. The function parses this string to return an XDM value"),
100-
new FunctionParameterSequenceType("options", Type.MAP, Cardinality.EXACTLY_ONE, "Parsing options")
101-
},
102-
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE, "The parsed data as XML")
103-
)
104-
};
72+
private static final String FS_JSON_DOC_NAME = "json-doc";
73+
static final FunctionSignature[] FS_JSON_DOC = functionSignatures(
74+
FS_JSON_DOC_NAME,
75+
"Reads an external (or database) resource containing JSON, and returns the results of parsing the resource as JSON. An URL parameter " +
76+
"without scheme or scheme 'xmldb:' is considered to point to a database resource.",
77+
returnsOpt(Type.ITEM, "The parsed data, typically a map, array or atomic value"),
78+
arities(
79+
arity(
80+
FS_PARAM_HREF
81+
),
82+
arity(
83+
FS_PARAM_HREF,
84+
FS_PARAM_OPTIONS
85+
)
86+
)
87+
);
88+
89+
private static final String FS_JSON_TO_XML_NAME = "json-to-xml";
90+
static final FunctionSignature[] FS_JSON_TO_XML = functionSignatures(
91+
FS_JSON_TO_XML_NAME,
92+
"Parses a string supplied in the form of a JSON text, returning the results in the form of an XML document node.",
93+
returnsOpt(Type.ITEM, "The parsed data as XML"),
94+
arities(
95+
arity(
96+
FS_PARAM_JSON_TEXT
97+
),
98+
arity(
99+
FS_PARAM_JSON_TEXT,
100+
FS_PARAM_OPTIONS
101+
)
102+
)
103+
);
105104

106105
public static final String OPTION_DUPLICATES = "duplicates";
107106
public static final String OPTION_DUPLICATES_REJECT = "reject";
@@ -139,9 +138,9 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathExce
139138

140139
JsonFactory factory = createJsonFactory(liberal);
141140

142-
if (isCalledAs("parse-json")) {
141+
if (isCalledAs(FS_PARSE_JSON_NAME)) {
143142
return parse(args[0], handleDuplicates, factory);
144-
} else if (isCalledAs("json-to-xml")) {
143+
} else if (isCalledAs(FS_JSON_TO_XML_NAME)) {
145144
return toxml(args[0], handleDuplicates, factory);
146145
} else {
147146
return parseResource(args[0], handleDuplicates, factory);
@@ -359,7 +358,6 @@ public static void jsonToXml(MemTreeBuilder builder, JsonParser parser) throws I
359358
if(parser.getCurrentName() != null){
360359
builder.addAttribute(KEY, parser.getCurrentName());
361360
}
362-
// according to spec, all numbers are converted to double
363361
builder.characters(parser.getText());
364362
builder.endElement();
365363

exist-core/src/test/xquery/xquery3/xml-to-json.xql

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ declare
5858
%test:arg('arg1', '')
5959
%test:assertError('FOJS0006')
6060
%test:arg('arg1', '0')
61-
%test:assertEquals('0.0')
61+
%test:assertEquals('0')
6262
%test:arg('arg1', '1')
63-
%test:assertEquals('1.0')
63+
%test:assertEquals('1')
6464
%test:arg('arg1', '-1')
65-
%test:assertEquals('-1.0')
65+
%test:assertEquals('-1')
6666
%test:arg('arg1', '01')
67-
%test:assertEquals('1.0') (: should error out for leading zeros according to spec :)
67+
%test:assertEquals('1')
6868
%test:arg('arg1', '08')
69-
%test:assertEquals('8.0') (: should error out for leading zeros according to spec :)
69+
%test:assertEquals('8')
7070
%test:arg('arg1', '3.1415')
7171
%test:assertEquals('3.1415')
7272
%test:arg('arg1', '0.31415e+1')
@@ -303,7 +303,7 @@ function xtj:xml-to-json-xmlInJsonString() {
303303
};
304304

305305
declare
306-
%test:assertEquals('{"pcM9qSs":"YbFYeK10.e01xgS1DEJFaxxvm372Ru","wh5J8qAmnZx8WAHnHCeBpM":-1.270212191431E9,"ssEhB3U9zZhRNNH2Vm":["A","OIQwg4ICB9fkzihwpE.cQv1",false]}')
306+
%test:assertEquals('{"pcM9qSs":"YbFYeK10.e01xgS1DEJFaxxvm372Ru","wh5J8qAmnZx8WAHnHCeBpM":-1270212191.431,"ssEhB3U9zZhRNNH2Vm":["A","OIQwg4ICB9fkzihwpE.cQv1",false]}')
307307
function xtj:xml-to-json-generatedFromSchema-1() {
308308
let $node :=
309309
<map xmlns="http://www.w3.org/2005/xpath-functions"
@@ -320,7 +320,7 @@ function xtj:xml-to-json-generatedFromSchema-1() {
320320
};
321321

322322
declare
323-
%test:assertEquals('{"v-DhbQUwZO3zpW":[{"fRcP.5e9btnuR3dOnd":[false,"_aQ",null],"yVlXSsyg1pPatQ7ilEaSSA9":"DVbrO2wpIRJimrskkRk.7wg1Gvh","K9xGofqp":true,"PatQ7iK9xGof":false},1.1145450201E7,5.84608693252E8],"IU6lSWbLYTzc3QvIVAdmJ.CG":1.600374222048E9,"_o3UT5zEy":"WFUwRRW5Jc3rdwKCoO8iV3RYDu_5"}')
323+
%test:assertEquals('{"v-DhbQUwZO3zpW":[{"fRcP.5e9btnuR3dOnd":[false,"_aQ",null],"yVlXSsyg1pPatQ7ilEaSSA9":"DVbrO2wpIRJimrskkRk.7wg1Gvh","K9xGofqp":true,"PatQ7iK9xGof":false},11145450.201,584608693.252],"IU6lSWbLYTzc3QvIVAdmJ.CG":1600374222.048,"_o3UT5zEy":"WFUwRRW5Jc3rdwKCoO8iV3RYDu_5"}')
324324
function xtj:xml-to-json-generatedFromSchema-2() {
325325
let $node :=
326326
<map xmlns="http://www.w3.org/2005/xpath-functions"
@@ -362,3 +362,71 @@ function xtj:xml-to-json-clearTextnodeBufferForNewElement() {
362362
let $node := <array><array> </array><string/></array>
363363
return fn:xml-to-json($node)
364364
};
365+
366+
declare
367+
%test:arg("int", "-1") %test:assertEquals('{"integer":-1}')
368+
%test:arg("int", "-1.0") %test:assertEquals('{"integer":-1.0}')
369+
%test:arg("int", "0") %test:assertEquals('{"integer":0}')
370+
%test:arg("int", "0.0") %test:assertEquals('{"integer":0.0}')
371+
%test:arg("int", "1") %test:assertEquals('{"integer":1}')
372+
%test:arg("int", "1.0") %test:assertEquals('{"integer":1.0}')
373+
function xtj:xmlmap-to-json-for-int-precision($int as xs:string) as xs:string {
374+
fn:xml-to-json(
375+
<map xmlns="http://www.w3.org/2005/xpath-functions">
376+
<number key="integer">{$int}</number>
377+
</map>
378+
)
379+
};
380+
381+
declare
382+
%test:arg("int", "-1") %test:assertXPath('$result/fn:map/fn:number = ''-1''')
383+
%test:arg("int", "-1.0") %test:assertXPath('$result/fn:map/fn:number = ''-1.0''')
384+
%test:arg("int", "0") %test:assertXPath('$result/fn:map/fn:number = ''0''')
385+
%test:arg("int", "0.0") %test:assertXPath('$result/fn:map/fn:number = ''0.0''')
386+
%test:arg("int", "1") %test:assertXPath('$result/fn:map/fn:number = ''1''')
387+
%test:arg("int", "1.0") %test:assertXPath('$result/fn:map/fn:number = ''1.0''')
388+
function xtj:json-to-xmlmap-for-int-precision($int as xs:string) as document-node() {
389+
fn:json-to-xml(
390+
'{"integer":' || $int || '}'
391+
)
392+
};
393+
394+
declare
395+
%test:arg("int", "1E9") %test:assertEquals('{"integer":1E+9}')
396+
%test:arg("int", "1E+9") %test:assertEquals('{"integer":1E+9}')
397+
%test:arg("int", "1E-9") %test:assertEquals('{"integer":1E-9}')
398+
%test:arg("int", "1e9") %test:assertEquals('{"integer":1E+9}')
399+
%test:arg("int", "1e+9") %test:assertEquals('{"integer":1E+9}')
400+
%test:arg("int", "1e-9") %test:assertEquals('{"integer":1E-9}')
401+
%test:arg("int", "1.1E9") %test:assertEquals('{"integer":1.1E+9}')
402+
%test:arg("int", "1.1E+9") %test:assertEquals('{"integer":1.1E+9}')
403+
%test:arg("int", "1.1E-9") %test:assertEquals('{"integer":1.1E-9}')
404+
%test:arg("int", "1.1e9") %test:assertEquals('{"integer":1.1E+9}')
405+
%test:arg("int", "1.1e+9") %test:assertEquals('{"integer":1.1E+9}')
406+
%test:arg("int", "1.1e-9") %test:assertEquals('{"integer":1.1E-9}')
407+
function xtj:xmlmap-to-json-for-exponent($int as xs:string) as xs:string {
408+
fn:xml-to-json(
409+
<map xmlns="http://www.w3.org/2005/xpath-functions">
410+
<number key="integer">{$int}</number>
411+
</map>
412+
)
413+
};
414+
415+
declare
416+
%test:arg("int", "1E9") %test:assertXPath('$result/fn:map/fn:number = ''1E9''')
417+
%test:arg("int", "1E+9") %test:assertXPath('$result/fn:map/fn:number = ''1E+9''')
418+
%test:arg("int", "1E-9") %test:assertXPath('$result/fn:map/fn:number = ''1E-9''')
419+
%test:arg("int", "1e9") %test:assertXPath('$result/fn:map/fn:number = ''1e9''')
420+
%test:arg("int", "1e+9") %test:assertXPath('$result/fn:map/fn:number = ''1e+9''')
421+
%test:arg("int", "1e-9") %test:assertXPath('$result/fn:map/fn:number = ''1e-9''')
422+
%test:arg("int", "1.1E9") %test:assertXPath('$result/fn:map/fn:number = ''1.1E9''')
423+
%test:arg("int", "1.1E+9") %test:assertXPath('$result/fn:map/fn:number = ''1.1E+9''')
424+
%test:arg("int", "1.1E-9") %test:assertXPath('$result/fn:map/fn:number = ''1.1E-9''')
425+
%test:arg("int", "1.1e9") %test:assertXPath('$result/fn:map/fn:number = ''1.1e9''')
426+
%test:arg("int", "1.1e+9") %test:assertXPath('$result/fn:map/fn:number = ''1.1e+9''')
427+
%test:arg("int", "1.1e-9") %test:assertXPath('$result/fn:map/fn:number = ''1.1e-9''')
428+
function xtj:json-to-xmlmap-for-exponent($int as xs:string) as document-node() {
429+
fn:json-to-xml(
430+
'{"integer":' || $int || '}'
431+
)
432+
};

0 commit comments

Comments
 (0)