Reputation: 121
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
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