In Web API, I have created a custom MediaTypeFormatter to create JSON output with a specific schema, making standard serialization unsuitable. Not that the context above may be relevant, but I need to convert an object, and also IEnumerable to JSON with a schema I specify.
In pseudo code:
If object is a collection
foreach item in collection
Write the name/type of the item (will be same for all)
foreach property of item
Write the property name, type, and value
Else
foreach property of object
Write the property name, type, and value
The part I am most interested in is acquiring a class property name/value via reflection.
For example, this sent from a controller:
return new MyPerson { .FirstName = "Bob", .DateOfBirth = Convert.ToDateTime("1979-03-01") }
...would be outputted as (crude example as the JSON is easy to change to craft the necessary schema):
{ "MyPerson" : {"FirstName": {"value": "Bob", "Type": "string"}}, "DateOfBirth": {"value": "1979-03-01", "Type": "date"}}
Likewise, a collection would be iterated to produce a similar output:
return new IEnumerable<Foo>() {
new Foo() { Param1 = "aaa", Param2 = "bbb" },
new Foo() { Param1 = "ccc", Param2 = "ddd" }
}
...producing
{ "FooCollection": [
{ "Foo" : {"Param1": {"value": "aaa", "Type": "string"}}, {"Param2": {"value": "bbb", "Type": "string"}} },
{ "Foo" : {"Param1": {"value": "ccc", "Type": "string"}}, {"Param2": {"value": "ddd", "Type": "string"}} }
]}
I have tried to digest many other examples (1,2) relating to this challenge, but am struggling to adapt them. This is as far as I've managed to get:
private void WriteStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders)
{
using (StringWriter _stringWriter = new StringWriter()) {
if (!(value is ICollection))
{
foreach (PropertyInfo p in value.GetProperties())
{
_stringWriter.Write(GetPropertyData(p));
}
}
else
{
foreach (object o in value)
{
foreach (PropertyInfo p in o.GetProperties())
{
_stringWriter.Write(GetPropertyData(p));
}
}
}
// output omitted for brevity...
}
}
public function GetPropertyData(PropertyInfo p) {
return string.Format("{name: '{0}', type: '{1}', value: '{2}'},",
p.Name,
p.PropertyType.ToString(),
p.GetValue(p).ToString())
}
I believe you are approaching your problem in the wrong way. Instead of reinventing the wheel by creating a custom MediaTypeFormatter
you should just use the correct models for your objects and let the serializer do the rest.
An example would be to use extension methods for your purpose:
public static class JsonExtensions
{
public static object CreateModels<T>(this IEnumerable<T> models, string modelName = null)
{
modelName = modelName ?? typeof(T).Name+"Collection";
return new Dictionary<string, object>()
{
{ modelName, models.Select(m => CreateModel(m)) }
};
}
public static IDictionary<string, object> CreateModel<T>(this T model, string modelName = null)
{
modelName = modelName ?? typeof(T).Name;
return new Dictionary<string, object>()
{
{ modelName, GetProperties(model) }
};
}
private static IDictionary<string, object> GetProperties<T>(T obj)
{
var props = typeof(T).GetProperties();
return props.ToDictionary(p => p.Name, p => (object)new { type = p.PropertyType.ToString(), value = p.GetValue(obj, null).ToString() });
}
}
Assuming you are using Json.NET in your project, this is how they are used:
JsonConvert.SerializeObject(new MyPerson { FirstName = "Bob", DateOfBirth = Convert.ToDateTime("1979-03-01") }.CreateModel());
Outputs (pretty printed):
{
"MyPerson": {
"FirstName": {
"type": "System.String",
"value": "Bob"
},
"DateOfBirth": {
"type": "System.DateTime",
"value": "3\/1\/1979 12:00:00 AM"
}
}
}
While:
JsonConvert.SerializeObject(new List<Foo>() {
new Foo() { Param1 = "aaa", Param2 = "bbb" },
new Foo() { Param1 = "ccc", Param2 = "ddd" }
}.CreateModels());
Outputs:
{
"FooCollection": [
{
"Foo": {
"Param1": {
"type": "System.String",
"value": "aaa"
},
"Param2": {
"type": "System.String",
"value": "bbb"
}
}
},
{
"Foo": {
"Param1": {
"type": "System.String",
"value": "ccc"
},
"Param2": {
"type": "System.String",
"value": "ddd"
}
}
}
]
}
.NET Fiddle Demo HERE
I see in your examples you are using string
and not System.String
as type name for the properties. Is not easy to use the aliased type names instead of the CLR true names, but if it is really necessary you may look at this answer for a way to obtain that.