Read JSON with embedded JS comments using Jackson

Viewed 874

I am looking for a way to process JSON, which includes JS comments. I know, comments a not legal for JSON, but unfortunately I have requirements to read/write JSON files with comments.

I have found a way to write comments using Jackson. This Code

JsonGenerator gen = factory.createGenerator(System.out);
gen.writeStartObject();
gen.writeStringField("a", "b");
gen.writeRaw("\n/*------------*/\n");
gen.writeStringField("c", "d");
gen.writeEndObject();
gen.close();

generates following JSON:

{"a":"b"
/*------------*/
,"c":"d"}

If I start parsing this JSON

factory.enable(JsonParser.Feature.ALLOW_COMMENTS);
JsonParser parser = factory.createParser("{\"a\":\"b\"/*------------*/,\"c\":\"d\"}");
while (parser.nextToken() != JsonToken.END_OBJECT) {
    System.out.println(parser.currentToken() + ":" + parser.getText());
}

comments and all the formatting are just skipped. There is not even a JsonToken of type like "RAW" or "COMMENT".

Is there a way to parse JSON with embedded raw data using Jackson (or other Java library)?

2 Answers

Dont know why you need to parse the commend from the json. If you like to see the commend from the json why dont you just read it json as string and parse the commend out. here is a regex example for reading the commend;

final Pattern pattern = Pattern.compile("/\\*.+\\*/", Pattern.DOTALL);
final Matcher matcher = pattern.matcher("{\"a\":\"b\"/*------------*/,\"c\":\"d\"}");
matcher.find();
System.out.println(matcher.group(0));

The NodeJs way

You can achieve this via a Node Package - node-comment-json

Basically, this library is specifically designed to maintain the comments as is and also allows to prettify the output of the JSON.

This is what I could do after installing the package using npm i -g comment-json:

$ node
> const { parse, stringify } = require('comment-json') 
> const parsed = parse(`{"a":"b"/*------------*/,"c":"d"}`)
> prased
{ a: 'b', c: 'd' }
> parsed['a'] = 'df'
> parsed
{ a: 'df', c: 'd' }
> stringify(parsed, null, 2)
'{\n  "a": "df" /*------------*/,\n  "c": "d"\n}'

Now, I know this is available as a node package. We can either use it via node or in case if its really necessary to use it via Java.

Java workaround

Installing NPM and the package:

Using the frontend-maven-plugin, you can install node/npm and also the package. So your pom.xml can look something like:

<plugin>
    ...
    <executions>
        <execution>
            <!-- optional: you dont really need execution ids -->
            <id>install node and npm</id>
            <goals>
                <goal>install-node-and-npm</goal>
            </goals>
            <!-- optional: default phase is "generate-resources" -->
            <phase>generate-resources</phase>

            <configuration>
                <nodeVersion>v6.9.1</nodeVersion>
            </configuration>
        </execution>
        <execution>
            <id>npm install</id>
            <goals>
               <goal>npm</goal>
            </goals>

            <!-- optional: default phase is "generate-resources" -->
            <phase>generate-resources</phase>

            <configuration>
            <!-- Install the comment-json-->
            <arguments>install -g comment-json</arguments>
            </configuration>
         </execution>
    </executions>
</plugin>

Create a NodeJs Script which would parse arguments and parse the required logic

I wrote a sample app.js script which excepts parameters like:

  • JSON: "{\"a\":\"b\"/*------------*/,\"c\":\"d\"}"
  • Key to Change: "a"
  • New value to change: "df"

app.js:

const {
  parse,
  stringify
} = require('comment-json');

 const parsed = parse(process.argv[2]);
 parsed[process.argv[3]] = process.argv[4];

 console.log(stringify(parsed, null, 2))

So if I execute it as: node app.js "{\"a\":\"b\"/*------------*/,\"c\":\"d\"}" "a" "df", it prints on the console:

{
  "a": "df" /*------------*/,
  "c": "d"
}

Use ProcessBuilder, execute the script and capture the output from console.log

Use IOUtils also

List<String> commands = new LinkedList<String>();
commands.add("node");
commands.add("app.js"); // You need the actual /path/to/app.js
commands.add("{\"a\":\"b\"/*------------*/,\"c\":\"d\"}");
commands.add("a");
commands.add("df");

ProcessBuilder processBuilder = new ProcessBuilder(commands);
String output = IOUtils.toString(processBuilder.start().getInputStream(), StandardCharsets.UTF_8);
Related