4

I'm intercepting a Web API 2 pipeline at the call to OnActionExecuted. Here, I'm turning the object returned by the action into an ExpandoObject, recursively (i.e any properties on the object that are themselves objects also get turned into ExpandoObjects, and so on down the hierarchy).

It XML serializes OK, but only as a dictionary (presumably because ExpandoObject implements IDictionary, and it just pulls the keys and values from that). I'd rather see it serialize as though it were an object with properties, not as a bunch of key/value pairs.

Any way to do this without authoring my own XML serializer?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
MiloDC
  • 2,373
  • 1
  • 16
  • 25

1 Answers1

2

You could wrap the ExpandoObject in a ISerializable implementation. It recursively wraps contained ExpandoObject.

[Serializable]
public class SerializableWrapper : ISerializable
{
    private IDictionary<string, object> _data;

    public IDictionary<string, object> Data
    {
        get { return _data; }
    }

    public SerializableWrapper(IDictionary<string, object> data)
    {
        _data = data;
    }

    protected SerializableWrapper(SerializationInfo info, StreamingContext context)
    {
        this._data = new Dictionary<string, object>();
        var enumerator = info.GetEnumerator();
        while (enumerator.MoveNext())
        {
            this._data[enumerator.Name] = enumerator.Value;
        }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {            
        foreach (var kvp in this._data)
        {
            info.AddValue(kvp.Key, Wrap(kvp.Value));
        }
    }

    private static object Wrap(object value)
    {
        var expando = value as ExpandoObject;
        if (expando != null)
        {
            return new SerializableWrapper(expando);
        }
        var expandoList = value as IEnumerable<ExpandoObject>;
        if (expandoList != null)
        {
            return expandoList
                .Select(Wrap)
                .Cast<SerializableWrapper>()
                .ToArray();
        }
        var list = value as IEnumerable;
        if (list != null && !(value is string))
        {
            return list
                .Cast<object>()
                .Select(Wrap)
                .ToArray();
        }
        return value;
    }
}
dynamic obj = new ExpandoObject();
obj.Foo = 3;
obj.Bar = new [] { new ExpandoObject() };
obj.Bar[0].Baz = "Qux";

var wrapped = new SerializableWrapper(obj);

var ser = new DataContractSerializer(typeof(SerializableWrapper), new [] { typeof(SerializableWrapper[]), typeof(object[]) });
var mem = new MemoryStream();
ser.WriteObject(mem, wrapped);

Generates:

<SerializableWrapper xmlns="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/2001/XMLSchema">
  <Foo i:type="x:int" xmlns="">3</Foo>
  <Bar i:type="a:ArrayOfSerializableWrapper" xmlns="" xmlns:a="http://schemas.datacontract.org/2004/07/">
    <a:SerializableWrapper>
      <Baz i:type="x:string">Qux</Baz>
    </a:SerializableWrapper>
  </Bar>
</SerializableWrapper>

The serialized XML is not pretty. You could play with a DataContractResolver or post-process the XML to make it less ugly.

To deserialize it again, you can use

mem.Position = 0;
var deserialized = (SerializableWrapper) ser.ReadObject(mem);

Another approach would be to implement IXmlSerializable and use XmlSerializer instead.

Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138