JSON.stringify on Java object

Attila Szegedi attila.szegedi at oracle.com
Mon Sep 16 23:14:10 PDT 2013


Ah, that's a common pitfall :-) So common in fact, that I'm devoting it few slides in my JavaOne talk next week. The gist of it is that JSON.stringify…

1. doesn't stringify POJOs,
2. can't be extended by Nashorn to stringify POJOs,
3. but there's help, as long as you don't mind writing some code yourself.

Let's go point by point.

The fact it doesn't stringify POJOs comes from ECMAScript 5.1 spec, section 15.12.3 <http://es5.github.io/#x15.12.3>. If you look up the definition of the abstract "Str" method, you'll see it is specifically defined to only work on native JS object, array, number, and boolean values. It doesn't work even for JS functions! And non-JS objects are off limits.

The fact it can't be extended by Nashorn internally to stringify POJOs comes from the sentence at end of 15.12.2, saying "It is not permitted for a conforming implementation of JSON.parse to extend the JSON grammars. If an implementation wishes to support a modified or extended JSON interchange format it must do so by defining a different parse function." Bummer…

As for there being help as long as you don't mind writing some code yourself: JSON.stringify can accept a "replacer" function as its second argument. Likewise, JSON.parse can accept a corresponding "reviver" function as its second argument. Here's an example for one way how'd you stringify/parse a Java java.util.BitSet object:

var b = new java.util.BitSet()
b.set(3)
b.set(5)

// See how BitSet is ignored by stringify by default: 
print(JSON.stringify(b))
print(JSON.stringify({ "b": b, "a": "hello" }))

// Let's define a replacer function
function replaceBitSet(key, value) {
    if(value instanceof java.util.BitSet) {
        return {
            __kind__: "BitSet",
            setBits: getSetBits(value)
        }
    }
    return value;
}

// When given a BitSet, returns an Array containing indices of set bits
// in increasing order.
function getSetBits(bitSet) {
    var setBits = new Array()
    for(var i = bitSet.nextSetBit(0); i != -1; i = bitSet.nextSetBit(i + 1)) {
        setBits.push(i)
    }
    return setBits
}

// See how stringify now creates a custom representation
var s = JSON.stringify(b, replaceBitSet)
print(s)

// Let's now define a corresponding reviver
function reviveBitSet(name, value) {
    if(value.__kind__ === "BitSet") {
        var setBits = value.setBits
        var bitSet = new java.util.BitSet()
        for each(var i in setBits) {
            bitSet.set(i)
        }
        return bitSet
    }
    return value
}

// Parse using our reviver
var b2 = JSON.parse(s, reviveBitSet)

// See that we got a BitSet back
print(b2 instanceof java.util.BitSet)
print(b2)
print(b2.equals(b1))

Unfortunately, I don't see how could we provide a generic default replacer/reviver combination that'd work for every POJO - we can't write one that is guaranteed to capture the full internal state of every Java object, nor be able to restore it. We can't (and wouldn't be willing anyway) to fool around with Java serialization mechanisms for this purpose, as that's a treachery slope; as a matter of fact, JSON is strictly less expressive than serialization as it can't stringify cyclic graphs, and will expand acyclic non-graph trees into trees (that is to say, if the same object is reachable through two paths from the root object, it'll be stringified twice).

That's why you have to write some code on your own for specific POJOs you want to stringify.

Hope this helps,
  Attila.

On Sep 17, 2013, at 3:14 AM, Viktor Gamov <viktor.gamov at gmail.com> wrote:

> Hello Nashorn team,
> 
> I have a question regarding conversion java object to json using JSON.stringify. Is it possible?
> Here is my test method
> 
> @Test
> public void convertToJson() throws ScriptException, NoSuchMethodException {
>    Person person = new Person();
>    person.setFirstName("Vik");
>    person.setLastName("Gamov");
>    person.setSsn("111-11-11");
>    Invocable invocable = (Invocable) engine;
>    Object x = invocable.invokeMethod(engine.eval("JSON"), "stringify", person);
>    System.out.println(x);
> }
> 
> Prints null. Please advise.
> 
> Thanks		
> 
> -- 
> With Best Regards, 
> Vik Gamov



More information about the nashorn-dev mailing list