Support for GetDynamicMemberNames() ?

Jul 30, 2011 at 2:34 PM
Edited Jul 30, 2011 at 2:38 PM

Hi,

I was testing if Clay could be used with RavenDb to store/retrieve shaped objects.

The first problem I face was that GetDynamicMemberNames() is not supported.

It seems that RavenDb JsonSerializer uses it to discover object properties.

In latests changes, I've seen that IClayBehavior exposes GetMembers(), maybe  a good start ?

Coordinator
Aug 3, 2011 at 12:13 AM

Possibly, yes. It should be a matter of creating a behavior specialized in exposing JSON objects, but there may actually already be some other, simpler implementations of dynamic over JSON?

Aug 4, 2011 at 8:29 AM

ExpandoObject doesn't work well with JsonSerializer because of the IDictionary<,> implementation : objects are returned as arrays of key/value pairs. (http://stackoverflow.com/questions/5156664/how-to-flatten-an-expandoobject-returned-via-jsonresult-in-asp-net-mvc)

I'm just speaking of serialization here. I don't see why a specialized behavior should be required. Could the PropBehavior just fit this case ? If I've undestand well, behaviors are executed in registration ordre, if PropBehavior handles GetMembers(), JsonSerializer will be able to discover all members. Note that it's only the first exception raised, I don't know if they will be other problems afterwards.

I've not event started to think about deserialization yet. I suppose you're right about the fact that we will need another behavior or something that the serializer is aware of to build clay objects. (or ClayFactory.GetAssigner already does that ?)

Bonus: if adding behaviors to extend clay functionalities is an interesting case, why not enhancing ClayFactory to allow additional behaviors to be passed to ClayFactoryBehavior.InvokeMember() when building Clay instance ?

Coordinator
Aug 5, 2011 at 8:18 PM

Sure, maybe. I suppose the only way to find out is to try it?

Jan 14, 2012 at 3:41 AM

After some messing around and head-scratching I got JSON serialization working. Deserialization should be pretty easy with an additional behavior.

Initially I tried fixing GetDynamicMemberNames (which itself is pretty easy), but even then Json.Encode didn't like it, I think it's to do with how it recognizes the type to convert in DynamicJavaScriptConverter, and there's even a comment there that it won't simply recognize the IDynamicMetaObjectProvider interface. So the working solution involves creating a custom JavaScriptConverter which is actually pretty straightforward:

    public class ClayJavaScriptConverter : JavaScriptConverter {
        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) {
            throw new NotSupportedException();
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) {
            var values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            
            // Get the value for each member in the dynamic object
            var clay = obj as IClayBehaviorProvider;
            clay.Behavior.GetMembers(() => null, clay, values);
            return values;
        }

        public override IEnumerable<Type> SupportedTypes {
            get {
                yield return typeof(Clay);
            }
        }

    }

    public ActionResult Test() {
            dynamic clay = new Clay(
                new ClaySharp.Behaviors.InterfaceProxyBehavior(),
                new ClaySharp.Behaviors.PropBehavior(),
                new ClaySharp.Behaviors.ArrayBehavior(),
                new ClaySharp.Behaviors.NilResultBehavior()
            );
            clay.Foo = "bar";
            var serializer = new JavaScriptSerializer();
            serializer.RegisterConverters(new[]{new ClayJavaScriptConverter()});
            return Content(serializer.Serialize((Clay)clay));
        }
    }

Output:

{"Foo":"bar"}

Success!

From here it's pretty easy to wrap this up in e.g. a JsonClayResult. Hope that helps (will be released in one of my modules soon, along with deserialization and XML support I hope...)

Jan 14, 2012 at 5:26 AM

A further note: this method has problems with Clay arrays, they just render empty; no way for the serializer to know to attempt to convert to a collection, it's still just looking for key/values. I have some ideas for creative solutions but perhaps tomorrow :)

Jan 31, 2012 at 2:14 AM
Edited Jan 31, 2012 at 2:15 AM

GetMembers is not implemented in ArrayBehavior.cs

try adding:

		public override object GetMembers(Func<object> proceed, object self, IDictionary<stringobject> members)
		{
			for(var i = 0; i < _data.Count; i++)
			{
				members.Add(i.ToString(), _data[i]);
			}
			return proceed();
		}

edit: remove line
Jan 31, 2012 at 4:55 PM

@remcoros - that wouldn't create a proper JSON array. Actually I now have a much better developed JSON converter, it fully handles both serialize and deserialize, and some other cool stuff. It's a library called "Putty" in the Alchemy module at http://scienceproject.codeplex.com - I'll be splitting it off into a separate library eventually.

Jan 31, 2012 at 11:19 PM

I see you replaced ArrayBehavior. still, it doesn't implement GetMembers, so how do you traverse it?

I came accross this when writing a html extension which outputs the structure of a clay object (using default clay factory).
with above fix, I can just recurse trough it using GetMembers.

Feb 1, 2012 at 12:31 AM

You don't need GetMembers for an Array.

Think about it: GetMembers implies a set of key/value pairs. An array is just a list of items that happens to be accessible by numerical index. From my Javascript Converter I cast the dynamic to an IEnumerable if it's being used as an array; the script serializer's normal processing then creates an ["a","b","c"] style JSON array automatically.

Feb 1, 2012 at 2:23 PM

I can see why GetMembers on ArrayBehavior wouldn't fit exactly, but still... I need a way to traverse the array, without resorting to customize or implement my own clay behaviors.

So using the default clay factory (and the default set of behaviors), how do I 'detect' the clay is used as an array.

Checking with 'obj is IEnumerable' won't work. Casting won't work (because Interface Proxying will kick in)

Feb 1, 2012 at 2:42 PM

Ok, I found a way, but I really don't like it:

This is part of my ClayWalker class:

			try
			{
				var e = ((dynamic)obj).GetEnumerator();
				if (e != null && e.MoveNext())
				{
					depth++;
					do
					{
						Walk(depth, e.Current);
					} while (e.MoveNext());
				}
			}
			catch (RuntimeBinderException ex)
			{
				
			}

The try/catch is needed. Because GetEnumerator() will fail on non-ArrayBehavior types.
Feb 1, 2012 at 2:52 PM
Edited Feb 1, 2012 at 3:01 PM

This is what I have now: https://gist.github.com/1717660

This is without the GetMembers on ArrayBehavior.

I use this in conjuction with a html extension method to output the structure of a clay object as a html comment.

 

If there is a more elegant way to do this, let me know please :-) (without customzing or adding own behaviors)

Feb 1, 2012 at 5:03 PM

It's a tricky one, and a question I was faced with. The problem is, how do you define whether it's an array or not, when it can be used as either an array or a dictionary?

You can very easily use obj.Count()>0 as a test, but it doesn't allow empty arrays.

So I think there isn't a neater way unless the swap out the array behavior for a new implementation. But that's the beauty of Clay, right? You can hook into IShapeTableEvents and inject your own behaviors ...

In Putty I have a concept of "Supertype" which basically means "the most likely kind of object this is being treated as". Various array manipulation operations will cause the supertype to change to Array (although simply enumerating it won't).

Putty has this whole extra behavior going on where if you add a property, it actually creates a new Putty. So you can do this;

 

dynamic putty = new Putty();
putty.Foo = new string[]{"Bar","Car"};
Assert.(putty.Foo.Supertype(),Is.EqualTo("Array"));

 

The test suite has a whole lot of interesting things like that going on ;)

Feb 2, 2012 at 12:27 PM

I created a ClayJsonConverter for Newtonsoft.JSON:

https://gist.github.com/1723468

I did not do extensive testing, but it seems to work fine for simple cases.

Aug 23, 2012 at 7:12 AM

@styx31

Did you find the answare to your question within the above discussion?

if it's so, can you please let us know about it?