X

Flexible JSON with Nashorn Parser API (JDK9)

Jim Laskey
Senior Development Manager

[Posted by Sundar] 

I recently came across Hjson - "the Human JSON - A configuration
file format that caters to humans and helps reduce the errors
they make". See also: http://hjson.org.

I wanted to see if I can use Nashorn Parser API (jdk9)
to support similar flexible JSON extension with Nashorn. In the following FlexiJSON.parse
implementation, Nashorn Parser API is used to validate that the
extendable flexi JSON is "data only" (That is, no executable code) and
then 'eval'uated to make an object out of it.

FlexiJSON allows the following:

  • single and mutliple line comments anywhere
  • non-quoted property names and values
  • regexp literal values
  • omitting trailing comma

When nashorn -scripting mode is enabled, FlexiJSON supports these
as well:

  • shell style # comments
  • multiple line (Unix heredoc style) string values
File: flexijson.js
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
function FlexiJSON() {}
// helper to locate Nashorn Parser API classes
FlexiJSON.treeType = function(name) {
return Java.type("jdk.nashorn.api.tree." + name);
}
// Nashorn Parser API classes used
FlexiJSON.ArrayLiteral = FlexiJSON.treeType("ArrayLiteralTree");
FlexiJSON.ExpressionStatement = FlexiJSON.treeType("ExpressionStatementTree");
FlexiJSON.ObjectLiteral = FlexiJSON.treeType("ObjectLiteralTree");
FlexiJSON.RegExpLiteral = FlexiJSON.treeType("RegExpLiteralTree");
FlexiJSON.Literal = FlexiJSON.treeType("LiteralTree");
FlexiJSON.Parser = FlexiJSON.treeType("Parser");
FlexiJSON.SimpleTreeVisitor = FlexiJSON.treeType("SimpleTreeVisitorES5_1");
// FlexiJSON.parse API
FlexiJSON.parse = function(str) {
var parser = (typeof $OPTIONS == "undefined")?
FlexiJSON.Parser.create() :
FlexiJSON.Parser.create("-scripting");
// force the string to be an expression by putting it inside (, )
str = "(" + str + ")";
var ast = parser.parse("", str, null);
// Should not happen. parse would have thrown syntax error
if (!ast) {
return undefined;
}
// allowed 'literal' values in flexi JSON
function isLiteral(node) {
return node instanceof FlexiJSON.ArrayLiteral ||
node instanceof FlexiJSON.Literal ||
node instanceof FlexiJSON.ObjectLiteral ||
node instanceof FlexiJSON.RegExpLiteral;
}
var visitor;
ast.accept(visitor = new (Java.extend(FlexiJSON.SimpleTreeVisitor)) {
lineMap: null,
throwError: function(msg, node) {
if (this.lineMap) {
var pos = node.startPosition;
var line = this.lineMap.getLineNumber(pos);
var column = this.lineMap.getColumnNumber(pos);
// we introduced extra '(' at start. So, adjust column number
msg = msg + " @ " + line + ":" + (column - 1);
}
throw new TypeError(msg);
},
visitLiteral: function(node, extra) {
print(node.value);
},
visitExpressionStatement: function(node, extra) {
var expr = node.expression;
if (isLiteral(expr)) {
expr.accept(visitor, extra);
} else {
this.throwError("only literals can occur", expr);
}
},
visitArrayLiteral: function(node, extra) {
for each (var elem in node.elements) {
if (isLiteral(elem)) {
elem.accept(visitor, extra);
} else {
this.throwError("only literal array element value allowed", elem);
}
}
},
visitObjectLiteral: function(node, extra) {
for each (var prop in node.properties) {
if (prop.getter != null || prop.setter != null) {
this.throwError("getter/setter property not allowed", node);
}
var value = prop.value;
if (isLiteral(value)) {
value.accept(visitor, extra);
} else {
this.throwError("only literal property value allowed", value);
}
}
},
visitCompilationUnit: function(node, extra) {
this.lineMap = node.lineMap;
var elements = node.sourceElements;
if (elements.length > 1) {
this.throwError("more than one top level expression", node.sourceElements[1]);
}
var stat = node.sourceElements[0];
if (! (stat instanceof FlexiJSON.ExpressionStatement)) {
this.throwError("only one top level expresion allowed", stat);
}
stat.accept(visitor, extra);
},
}, null);
// safe to eval given string as flexi JSON!
return eval(str);
}

Sample simple usage of FlexiJSON.parse would be as follows:

File: fjson.js
var obj = FlexiJSON.parse(<<EOF
// this is a comment
{
foo: 23,
bar: [ 34, 454, 54,],
// inline comment here
/** multi line
comments are fine too! */
# shell style line comment is fine!
regex: /gdfg/i, // regexp literal
str: <<END
Multiple line strings via nashorn
-scripting mode extension as well
END
}
EOF)
print(obj.foo);
print(obj.bar);
print(obj.regex);
print(obj.str);

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
Oracle

Integrated Cloud Applications & Platform Services