Nesting objects into arrays in return value

Sep 8, 2011 at 1:01 PM

Hi all,

Jint has allowed for some pretty interesting stuff on the project I'm working on - we're working on a JS based client-side API that uses our .NET business logic. However, I'm having hard time to sort out proper support for objects and arrays in the values returned by the API call.

The foundations of the API have been developed by another guy and I'm fixing some issues but having struggled with it for a few days already I have a fairly good understanding of what's going on. Briefly, there is a .NET handler (IHttpHandler) that handles the request and according to the URL pattern picks a .js file to be executed with JintEngine.Run(). The object returned then has to be serialized to JSON to be consumed by client-side JavaScript.

Here I'm facing two problems:

1.Objects into Arrays

Object nested into an Array doesn't seem to work. For example, the JavaScript function returns the following:

{ 'field1': ['listitem1', {'nestedfield1': 'nestedvalue1'}] }

which, upon inspection on the server side, appears as:

field1 : array
field1["0"] - 'listitem1' (String)
field1["1"] - undefined

Here is also what QuickWatch looks like, showing the value as null:

 

Considering that I'm not interfering with the returned value in any way (other than wrapping it in an object) I'm fairly confident that it's a Jint that can't cope with object within an array - but I'll be happy if someone can confirm this.

Nesting arrays multiple time works fine, and nesting objects within objects on many levels also works - it's just objects within arrays that come out undefined (null). I'm using Jint 0.9.0 (there was nothing in the release notes of 0.9.2 that suggests that this has been sorted), with .NET 3.5

 

2. Objects into Objects

This part is fine on the Jint side - I do see all the objects in the hierarchy on the server side, but I still haven't come up with a solution for the serialization to JSON. That's a problem that I'll eventually be able to solve on my own but if someone can give me some hints I will be most grateful.

I had a similar problem with nested arrays that came out as JsArray objects, which I then enumerate recursively and convert to object[], which serialize fine. With objects though it's not that simple.

a) I was thinking about constructing anonymous object on the fly, first with hardcoded property names matching the ones from the JS object - but I don't know if I can do this on the fly, with dynamic property names (and anonymous types also fail to serialize with DataContractJsonSerializer, although other serializers should work).|
b) Another option is to use ExpandoObject - but that's only available in .NET 4, so we'll have to migrate the whole project, which is going to be a massive, painful exercise (and we'll probably just drop the requirement for now if it comes to this)
c) The third option is to create a type on the fly with Reflection.Emit - this seems most likely to produce a serializable type (I think I can implement interfaces with dynamically created types)

Has anyone tried any of these?

 

Thanks for all suggestions,

Stefan

Developer
Sep 8, 2011 at 7:31 PM
Edited Sep 8, 2011 at 7:35 PM

"Value" property holds a CLR object which represents an internal value of the js object, for a primitive js objects like numbers, strings or marshaled CLR objects this property will be a some object, but for the pure javascript object this property will be null.

The side effect is the way how JintEngine.Run() handles returned values - it tries to unwrap them simply by returning "Value" property, therefore a pure js object will be returned as 'null'.

To workaround this issue you need to use the other overload of JintEngine.Run method and explicitly specify not to unwrap returned values.

Sep 14, 2011 at 4:09 PM

Thanks for your reply, Cin - I thought I have checked everything obvious but as usual it turns out I haven't - I figured out I can get the object properties by foreach (it implements IEnumerable) although the value shows as null - so I don't get an object but a collection of property/value pairs, which is fine. And this actually works for both settings of unwrap.

I was preparing to ask a further question about  about point 2 but while trying to describe what I've tried in detail I figured out a way to do it :-)

I tried constructing objects at runtime with Reflection.Emit and decorating them with the KnownTypeAttribute, so that DataContractJsonSerializer will process them. This didn't work, however, as the KnownTypeAttribute constructor takes a parameter - the known type - but it's not ready yet as it's being constructed at the moment, leading to a catch 22 - CustomAttributeBuilder wants the type sent in the constructor, and the type needs the CustomAttributeBuilder.SetCustomAttribute to be called before the type is generated.

Another approach I was considering was to add the dynamic types to a list and pass this list to the DataContractJsonSerializer constructor - but this couldn't work either, as the list can be generated only while serialization takes place (during the DataContractJsonSerializer.WriteObject call), and the list of KnownTypes should be provided before that - another catch 22!

So finally I defined my own ISerializable type, decorated it with SerializableAttribute, which servers only to wrap a Dictionary<string, object> that holds the object properties. So whenever I encounter a JsObject in the object tree to be serialized - I construct an instance of my class and add the properties of the property name/value pairs from the JsObject as KeyValuePairs to the dictionary, and I add this object with SerializationInfo.AddValue

This works, although there is this side effect that the type name of my class, along with the namespace, is added to the resulting JSON as another property of the object. This doesn't hurt in any way while parsing but makes the JSON look ugly. If you pick a short namespace name (or maybe declare it in the default namespace) instead of the fully qualified name this mitigates the problem with polluting the JSON. Other than that, I couldn't figure out a way to remove this autogenerated property - it seems that DataContractJsonSerializer just works like that.