[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