user3868244
user3868244

Reputation: 121

Change property name to the derived class name it has been instantiated with on serialization with Json.net

I am trying to serialize (and deserialize) my objects in .NET (standard) using the latest version of NewtonSoft.JSON.

Here are my classes :

public class BaseRequest
{
    public string UserName{ get; set; }
}

A request that derives from BaseRequest (I have many other such classes that derive from the BaseRequest):

public class GetDeviceRequest : BaseRequest
{
    public string SerialNumber { get; set; }
}

The request that I serialize to Json is created with the class :

public class CommunicationRequest
{
    public CommunicationRequest() { }

    public CommunicationRequest(BaseRequest baseRequest, string version ="1.1")
    {
        this.Version = version;
        this.Request = baseRequest;
    }

    public string Version { get; set; } = "1.1";
    public BaseRequest Request { get; set; }
}

Code I am using for serialization. I have not yet tested deserialization, but it should work the same way

GetDeviceRequest getDeviceRequest = new GetDeviceRequest()
{
    SerialNumber = "123456789",
    UserName = "john.doe"
};

CommunicationRequest request = new CommunicationRequest(getDeviceRequest);

var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver()
};

var json = JsonConvert.SerializeObject(request, settings);

What I get as JSON:

{
   "version":"1.1",
   "request":{      
         "serialNumber":"123456789",
         "username":"john.doe"
   }
}

What I want:

{
   "version":"1.1",
   "getDeviceRequest":{      
         "serialNumber":"123456789",
         "username":"john.doe"
   }
}

I need to change JsonProperty name of Request to GetDeviceRequest dynamically based on what the name of the derived class is.

What I could have done :

[JsonProperty("DerievedClassName")]
public BaseRequest Request { get; set; }

This does not obviously work for me because the object is not instantiated and I cannot get the type to use something like Request.GetType().

It would seem that the way is to implement my ICustomContractResolver, but I am not able to get a working CustomContractResolver for my use case.

Is there a way I can serialize the way I want to?

Upvotes: 2

Views: 995

Answers (1)

Brian Rogers
Brian Rogers

Reputation: 129807

You will need a custom JsonConverter for your CommunicationRequest class to handle the dynamic property name and also to be able to resolve it back to the correct type on deserialization (assuming you want to go round-trip with this). Something like the following should suit your needs, but may need some tweaking.

public class CommunicationRequestConverter : JsonConverter
{
    NamingStrategy NamingStrategy { get; set; }

    public CommunicationRequestConverter(NamingStrategy namingStrategy)
    {
        NamingStrategy = namingStrategy;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(CommunicationRequest);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        CommunicationRequest req = (CommunicationRequest)value;
        JObject jo = new JObject(
            new JProperty(GetPropertyName("Version"), req.Version),
            new JProperty(GetPropertyName(req.Request.GetType().Name), 
                          JObject.FromObject(req.Request, serializer))
        );
        jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);

        string versionPropertyName = GetPropertyName("Version");
        JProperty requestProperty = jo.Properties().FirstOrDefault(p => p.Name != versionPropertyName);

        Type baseRequestType = Assembly.GetAssembly(typeof(BaseRequest)).GetTypes()
            .Where(t => t.IsClass && GetPropertyName(t.Name) == requestProperty.Name)
            .First();

        CommunicationRequest req = new CommunicationRequest
        {
            Version = (string)jo[versionPropertyName],
            Request = (BaseRequest)requestProperty.Value.ToObject(baseRequestType, serializer)
        };

        return req;
    }

    private string GetPropertyName(string name)
    {
        return NamingStrategy.GetPropertyName(name, false);
    }
}

Set up your serializer settings like this:

var ns = new CamelCaseNamingStrategy();
var settings = new JsonSerializerSettings { 
    ContractResolver = new DefaultContractResolver { NamingStrategy = ns },
    Converters = new List<JsonConverter> { new CommunicationRequestConverter(ns) },
    Formatting = Formatting.Indented
};

Here is a round-trip demo: https://dotnetfiddle.net/kufBae

Upvotes: 1

Related Questions