Fast passing of JSON between JS and Java (and vice versa)

Tim Fox timvolpe at gmail.com
Thu Nov 27 19:08:25 UTC 2014


On 27/11/14 14:48, Attila Szegedi wrote:
> So, some initial discussion of this with my team leads to following conclusions:
>
> - We can't stop wrapping all objects in ScriptObjectMirror, as ScriptObjectMirror is a public class, and we allow people to expect it. If we now started returning ScriptObjectMirror sometimes and ArrayMirror (provisional name) other times, that'd be an API breaking change. That's sad, really – we should've probably never made ScriptObjectMirror public and instead forced people to only program against the JSObject interface instead.
>
> - We've been thinking of creating a separate "class ArrayMirror implements JSObject, List<Object>" for wrapping JS Arrays, but you'd need to explicitly ask a mirror that'll return these transitively, e.g. we could give you a Java.toJSONCompatible(obj) API that you'd use as: "myObject.expectsJSON(Java.toJSONCompatible(someJson));" You'd still be getting a ScriptObjectMirror on the top level (as long as it ain't an array in which case the top level would itself be an ArrayMirror), but it'd be carrying a hidden flag that'd change its behavior so whenever you retrieve an Array from it, it gets wrapped into ArrayMirror and not ScriptObjectMirror. Also, if you retrieve an Object from it, you'd get a ScriptObjectMirror with this flag propagated, so Arrays at any nesting depth would always be exposed as Lists. Arguably, this could be the default behaviour except for the fact that it isn't how it worked since the initial 8 release and we can't break backwards compatibility…
>
> How's that sound?


Sounds good. Thanks for taking time to look at this :)

>
> Attila.
>
> On Nov 27, 2014, at 2:46 PM, Attila Szegedi <attila.szegedi at oracle.com> wrote:
>
>> Also, the documentation for both List and Map interfaces prescribes an exact algorithm[1][2] that every implementation of them must use to calculate their hashCode(), and they too are incompatible. This is not as insurmountable as a javac error, but still not a good idea to violate. FWIW, having a separate ArrayMirror that implements only List<Object> might still be workable.
>>
>> Attila.
>>
>> ---
>> [1] http://docs.oracle.com/javase/8/docs/api/java/util/List.html#hashCode--
>> [2] http://docs.oracle.com/javase/8/docs/api/java/util/Map.html#hashCode--
>>
>> On Nov 27, 2014, at 2:40 PM, Attila Szegedi <attila.szegedi at oracle.com> wrote:
>>
>>> [...]
>>>
>>> Unfortunately, we can't subclass ScriptObjectMirror to give you an ArrayMirror as no Java class can simultaneously implement both List and Map interfaces due to incompatibility in return types of "Object Map.remove(Object)" and "boolean List.remove(Object)" :-( Trust me, I was quite mad when I first realized this.
>>>
>>> [...]
>>>
>>> Attila.
>>>
>>> On Nov 27, 2014, at 2:11 PM, Tim Fox <timvolpe at gmail.com> wrote:
>>>
>>>> As you know..
>>>>
>>>> In JS, a JSON Object is represented by a JS object, and in the Java world it's often represented by Map<String, Object>.
>>>> In JS a JSON array is represented by a JS array, and in the Java world it's often represented by a List<Object>.
>>>>
>>>> I'd love to be able to pass JSON between JS and Java and vice versa with the minimum of performance overhead. This is particularly important in Vert.x as we chuck a lot of JSON around.
>>>>
>>>> Let's say I have a Java interface which expects some JSON:
>>>>
>>>> interface SomeInterface {
>>>>
>>>>   void expectsJSON(Map<String, Object> json);
>>>> }
>>>>
>>>> Right now I am converting from JS-->Java as follows.
>>>>
>>>> var someJson = { foo: "bar"};
>>>> String encoded = JSON.stringify(someJson);
>>>> Map<String, Object> map = SomeJavaJSONLibrary.decode(encoded);
>>>> myObject.expectsJSON(map);
>>>>
>>>> As you can see it's pretty clunky. The other direction is equally as clunky. And it's slow as we're encoding/decoding everything via String.
>>>>
>>>> Then I realised that if I pass a JS object directly into the expectsJSON method Nashorn will provide me with a Map<String, Object> that backs the original object. I.e. I can do this:
>>>>
>>>> var someJson = { foo: "bar"};
>>>> myObject.expectsJSON(map);
>>>>
>>>> Yay! No encoding overhead. Fast. :)
>>>>
>>>> And it works with nested json:
>>>>
>>>> var someJson = { foo: "bar", nested: { wibble: "blah"}};
>>>>
>>>> Just when I was getting my hopes up that this would be a great super fast way of transferring JSON betwen Java and JS, I tried it with a nest array:
>>>>
>>>> var someJson = { foo: "bar", nestedArray: [123, 456]};
>>>>
>>>> But in Java, map.get("nestedArray") returns a ScriptObjectMirror not a List as I was hoping. :(
>>>>
>>>> So.. passing from JS to Java: JS Object maps to Map, but JS Array maps to ScriptObjectMirror. (Seems a bit asymmetric?).
>>>>
>>>> Any reason why we can't map JS Array to Java list when calling JS->Java? (Perhaps this is related to my previous question backing a JS Array with a List...)
>>>>
>>>> Do you have any other suggestions for transferring JSON between JS and Java without too much encoding overhead?
>>>>
>>>> Thanks again!
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>




More information about the nashorn-dev mailing list