Suppose I have following model class:
public class Action
{
public enum Type
{
Open,
Close,
Remove,
Delete,
Reverse,
Alert,
ScaleInOut,
Nothing
}
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("active")]
[JsonConverter(typeof(IntToBoolConverter))]
public bool Active { get; set; }
[JsonProperty("type")]
[JsonConverter(typeof(ActionTypeConverter))]
public Type ActionType { get; set; }
[JsonProperty("result")]
[JsonConverter(typeof(ActionResultConverter))]
public ActionResult Result { get; set; }
}
and I want to deserialize following JSON into that class:
{
"name":"test1",
"id":"aa0832f0508bb580ce7f0506132c1c13",
"active":"1",
"type":"open",
"result":{
"property1":"buy",
"property2":"123.123",
"property3":"2016-07-16T23:00:00",
"property4":"768",
"property5":true
}
}
Result object can be different each time (one of 6 models) and its type depends on JSON property type
.
I have created custom ActionResultConverter
(JsonConverter
annotation above Result
property of Action
class) that should be able to create specific result
object based on string in type
property of JSON.
My problem is that I don't know how to access that property from converter because only the result
part of whole JSON is passed to JsonReader
.
Any ideas or help will be appreciated.
Thanks!
Json.NET does not provide a method to access the value of a property of a parent object in the JSON hierarchy while deserializing a child object. Likely this is because a JSON object is defined to be an unordered set of name/value pairs, according to the standard, so there can be no guarantee the desired parent property occurs before the child in the JSON stream.
Thus, rather than handling the Type
property in a converter for ActionResult
, you'll need to do it in a converter for Action
itself:
[JsonConverter(typeof(ActionConverter))]
public class Action
{
readonly static Dictionary<Type, System.Type> typeToSystemType;
readonly static Dictionary<System.Type, Type> systemTypeToType;
static Action()
{
typeToSystemType = new Dictionary<Type, System.Type>
{
{ Type.Open, typeof(OpenActionResult) },
};
systemTypeToType = typeToSystemType.ToDictionary(p => p.Value, p => p.Key);
}
public static Type SystemTypeToType(System.Type systemType)
{
return systemTypeToType[systemType];
}
public static System.Type TypeToSystemType(Type type)
{
return typeToSystemType[type];
}
public enum Type
{
Open,
Close,
Remove,
Delete,
Reverse,
Alert,
ScaleInOut,
Nothing
}
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("active")]
[JsonConverter(typeof(IntToBoolConverter))]
public bool Active { get; set; }
[JsonProperty("type")]
[JsonConverter(typeof(ActionTypeConverter))]
public Type ActionType { get; set; }
[JsonProperty("result")]
public ActionResult Result { get; set; }
}
class ActionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var action = existingValue as Action ?? (Action)contract.DefaultCreator();
// Remove the Result property for manual deserialization
var result = obj.GetValue("Result", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();
// Populate the remaining properties.
using (var subReader = obj.CreateReader())
{
serializer.Populate(subReader, action);
}
// Process the Result property
if (result != null)
action.Result = (ActionResult)result.ToObject(Action.TypeToSystemType(action.ActionType));
return action;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}
Notice the use of JsonSerializer.Populate()
inside ReadJson()
. This automatically fills in all properties of Action
other than Result
, avoiding the need for manual deserialization of each.
Inspired by http://json.codeplex.com/discussions/56031:
public sealed class ActionModelConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(ActionModel).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
ActionModel actionModel = new ActionModel();
// TODO: Manually populate properties
actionModel.Id = (string)jObject["id"].ToObject<string>();
var type = (ActionModel.Type)jObject["type"].ToObject<ActionModel.Type>();
switch (type)
{
case ActionModel.Type.Open:
var actionResult = jObject["result"].ToObject<ActionOpenResult>(jsonSerializer);
default:
throw new JsonSerializationException($"Unsupported action type: '{type}'");
}
actionModel.Result = actionResult;
return actionModel;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Coded in editor, so sorry for typos :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With