4

I'm trying to map dynamic DTO to JSON or XML but to have information about fields types. So I'm interested to find out is it possible from object which looks looks this:

public class AddressDto
    {
        public string Street { get; set; }
    }
public class UserInfoDto
    {
        public string UserName { get; set; }            
        public int Age { get; set; }
        public AddressDto Address { get; set; }
    }

To get something like this in JSON (or XML):

{
    "fieldType": "UserInfoDto"
    "objectValue":
    {
        {
            "fieldType": "string",
            "fieldName": "UserName",
            "fieldValue": "John Doe",
        },
        {
            "fieldType": "integer",
            "fieldName": "Age",
            "fieldValue": "27",
        },
        {
            "fieldType": "AddressDto",
            "fieldName": "Address",
            "fieldValue": {
                "fieldType": "string",
                "fieldName": "Street",
                "fieldValue": "Lorem Ipsum"
            }
        }
    }
}

...and vice versa.

ShP
  • 1,143
  • 1
  • 12
  • 41
  • Do you want this to be automatically? Because I have a manual solution on mind (tedious actually). – Yanire Romero Sep 02 '14 at 21:05
  • Automatic will be better. I can do this by using reflection manually to generate some custom JSON or XML, but I'm having some issues when I need to convert it back to source object. So if you have any solution feel free to share it. – ShP Sep 03 '14 at 09:24

1 Answers1

1

Yes it's possible, but you have to do some work to instruct the serializer how to format the output string. If you want to stick with built-in .NET serializers, you can achieve this using the System.Runtime.Serialization.Json.DataContractJsonSerializer class.

1- Create the MetadataObject class as a wrapper object for output data

Define the following class and mark it with [DataContract] so it can be serialized:

[DataContract]
public class MetadataObject
{
    [DataMember(Name = "fieldType")]
    public string FieldType { get; set; }

    [DataMember(Name = "fieldName")]
    public string FieldName { get; set; }

    [DataMember(Name = "fieldValue")]
    public object FieldValue { get; set; }
}

2- Tell the serializer how to serialize the parent (UserInfoDto) object

To do so, you'll need to have your UserInfoDto object implement the ISerializable interface (more specifically, the GetObjectData() method) and mark it as [Serializable]. You also need to include all the custom types that this class will include. In this case, it would be the AddressDto type, as well as List<MedataObject> which gets constructed in the GetObjectData() method. The final class looks like this:

[KnownType(typeof(AddressDto))]
[KnownType(typeof(List<MetadataObject>))]
[Serializable]
public class UserInfoDto : ISerializable
{
    public string UserName { get; set; }

    public int Age { get; set; }

    public AddressDto Address { get; set; }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        var nodes = this.GetType().GetProperties().Select(property =>
        {
            return new MetadataObject
            {
                FieldType = property.PropertyType.Name,
                FieldName = property.Name,
                FieldValue = property.GetValue(this, null)
            };
        }).ToList();

        info.AddValue("fieldType", this.GetType().Name);
        info.AddValue("objectValue", nodes);
    }
}

Note that the GetObjectData() method uses reflection and creates a MetadataObject class for each property. The beauty of this is that it makes the code a little more generic. So later on if you decide you need more classes like UserInfoDto, you can put this logic in a base class and have other classes inherit from it.

3- Tell the serializer how to serialize the child (AddressDto) object

Same as UserInfoDto, have the child class implement ISerializable and mark it as [Serializable]:

[Serializable]
public class AddressDto : ISerializable
{
    public string Street { get; set; }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        foreach (var property in this.GetType().GetProperties())
        {
            info.AddValue("fieldType", property.PropertyType.Name);
            info.AddValue("fieldName", property.Name);
            info.AddValue("fieldValue", property.GetValue(this, null));
        }
    }
}

4- Put it all together

Finally, define your serializer like this:

DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(UserInfoDto));

var data = new UserInfoDto { Age = 30, UserName = "John" };
data.Address = new AddressDto { Street = "123 ABC" };

using (MemoryStream stream = new MemoryStream())
{
    using (StreamReader reader = new StreamReader(stream))
    {
        serializer.WriteObject(stream, data);
        stream.Position = 0;

        var output = reader.ReadToEnd();
    }
}

When you run this, output looks like this:

{  
   "fieldType":"UserInfoDto",
   "objectValue":[  
      {  
         "__type":"MetadataObject:#StackOverflow.Console",
         "fieldName":"UserName",
         "fieldType":"String",
         "fieldValue":"John"
      },
      {  
         "__type":"MetadataObject:#StackOverflow.Console",
         "fieldName":"Age",
         "fieldType":"Int32",
         "fieldValue":30
      },
      {  
         "__type":"MetadataObject:#StackOverflow.Console",
         "fieldName":"Address",
         "fieldType":"AddressDto",
         "fieldValue":{  
            "__type":"AddressDto:#StackOverflow.Console",
            "fieldType":"String",
            "fieldName":"Street",
            "fieldValue":"123 ABC"
         }
      }
   ]
}

Note that the __type properties are automatically generated by the serializer. If you're using .NET 4.5, you can try the following to have it not be part of the output (chances are you're going to need them if the string needs to be deserialized back into an object)

Community
  • 1
  • 1
Arian Motamedi
  • 7,123
  • 10
  • 42
  • 82
  • This is a nice solution, but I need this to work with any custom dto which I can not modify like this (I can't add [Serializable] anotation or any other changes). It should work with objects loaded via reflection from third party assemblies. – ShP Sep 03 '14 at 09:21
  • In that case you will need to define your own data contract serializer, which frankly, .NET Json libraries will make it a pain for you. Take a look at Json.NET. – Arian Motamedi Sep 03 '14 at 14:39