I am trying to serialize objects with the library ServiceStack.Text . This works
using System.Dynamic;
using ServiceStack.Text;
var x = new {Value= 10, Product = "Apples"};
Console.WriteLine(JsonSerializer.SerializeToString(x));
I get, as I expect
{"Value":10,"Product":"Apples"}
However
dynamic x = new ExpandoObject();
x.Value = 100;
x.Product = "Apples";
Console.WriteLine(JsonSerializer.SerializeToString(x));
I get to my surprise
[{"Key":"Value","Value":100},{"Key":"Product","Value":"Apples"}]
Why! What's going on?
Secondly, how can I get what I want?
ExpandoObject
implements IConnection<KeyValuePair>
and IEnumerable<KeyValuePair>
:
public sealed class ExpandoObject :
IDynamicMetaObjectProvider,
IDictionary<string, object>,
ICollection<KeyValuePair<string, object>>,
IEnumerable<KeyValuePair<string, object>>,
IEnumerable, INotifyPropertyChanged
My guess is that internally, the ServiceStack serializer is treating the ExpandoObject
as an IEnumerable<KeyValuePair>
, and so it serializes to a JSON array of key/value pairs.
This differs from your first (working) code snippet because .NET actually builds a real (anonymous) class for your data, basically it makes:
public class SomeNameTheCompilerMakesUp {
internal int Value { get; set; }
internal string Product { get; set; }
}
for you automatically, so when it is sent to the serializer, it is working with a real class with real properties, whereas the ExpandoObject
is really backed by an object[]
internally.
On a side-note, Microsoft's System.Web.Helpers.Json
behaves the same way. This test passes:
[TestMethod]
public void ExpandoObjectSerializesToJsonArray()
{
dynamic anonType = new { Value = 10, Product = "Apples" };
dynamic expando = new ExpandoObject();
expando.Value = 10;
expando.Product = "Apples";
var anonResult = System.Web.Helpers.Json.Encode(anonType);
var expandoResult = System.Web.Helpers.Json.Encode(expando);
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", anonResult);
Assert.AreEqual("[{\"Key\":\"Value\",\"Value\":10},{\"Key\":\"Product\",\"Value\":\"Apples\"}]", expandoResult);
}
One final edit:
You can make this work the way you want by turning your ExpandoObject
into a Dictionary<string, object>
. The caveat to this code is that it duplicates the data into a dictionary, so you have 2 copies in memory (or slightly less than, since technically the strings might be interned).
[TestMethod]
public void TestMethod1()
{
dynamic expando = new ExpandoObject();
expando.Value = 10;
expando.Product = "Apples";
// copy expando properties to dictionary
var dictionary = ((ExpandoObject)expando).ToDictionary(x => x.Key, x => x.Value);
var expandoResult = System.Web.Helpers.Json.Encode(expando);
var dictionaryResult = System.Web.Helpers.Json.Encode(dictionary);
Assert.AreEqual("[{\"Key\":\"Value\",\"Value\":10},{\"Key\":\"Product\",\"Value\":\"Apples\"}]", expandoResult);
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", dictionaryResult);
}
Although, for anyone that comes across this later, and is actually using System.Web.Helpers.Json
, the better thing to do is just wrap your ExpandoObject
in a DynamicJsonObject
like this:
[TestMethod]
public void TestMethod1()
{
dynamic expando = new ExpandoObject();
expando.Value = 10;
expando.Product = "Apples";
var dictionaryResult = System.Web.Helpers.Json.Encode(new DynamicJsonObject(expando));
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", dictionaryResult);
}
Ans once I worked through that, I found a similar question here: How to flatten an ExpandoObject returned via JsonResult in asp.net mvc?