I am programming against a third party API which returns JSON data, but the format can be a little strange. Certain properties can either be an object (which contains an Id
Rather than creating two separate converters for each of the fields, it would be wise to create a single converter for the "main" property and link the other one to it. ChildObjectId
is derived from the ChildObject
.
public class MyObject
{
[JsonIgnore]
public string ChildObjectId
{
get { return ChildObject.Id; }
// I would advise against having a setter here
// you should only allow changes through the object only
set { ChildObject.Id = value; }
}
[JsonConverter(typeof(MyObjectChildObjectConverter))]
public ChildObject ChildObject { get; set; }
}
Now to convert the ChildObject
can be a bit of a challenge. There are two possible representations of the object: a string or an object. Determine what representation you have and perform the conversion.
public class MyObjectChildObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ChildObject);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = serializer.Deserialize<JToken>(reader);
switch (obj.Type)
{
case JTokenType.Object:
return ReadAsObject(obj as JObject);
case JTokenType.String:
return ReadAsString((string)(JValue)obj);
default:
throw new JsonSerializationException("Unexpected token type");
}
}
private object ReadAsObject(JObject obj)
{
return obj.ToObject<ChildObject>();
}
private object ReadAsString(string str)
{
// do a lookup for the actual object or whatever here
return new ChildObject
{
Id = str,
};
}
}
Try this (extend it with some thorough validation if you'll be using it in your code):
public class MyObject
{
public ChildObject MyChildObject;
public string MyChildObjectId;
[JsonProperty("ChildObject")]
public object ChildObject
{
get
{
return MyChildObject;
}
set
{
if (value is JObject)
{
MyChildObject = ((JToken)value).ToObject<ChildObject>();
MyChildObjectId = MyChildObject.Id;
}
else
{
MyChildObjectId = value.ToString();
MyChildObject = null;
}
}
}
}
Here is what I would do in this situation.
ChildObject
JsonConverter
which can inspect the JSON and either:
After deserialization, if there was a ChildObject
property in the JSON (with either an ID or a full object value), you are guaranteed to have a ChildObject
instance and you can get its ID from it; otherwise, if there was no ChildObject
property in the JSON, the ChildObject
property in the parent class will be null.
Below is a full working example to demonstrate. In this example, I modified the parent class to include three separate instances of the ChildObject
to show the different possibilities in the JSON (string ID only, full object and neither present). They all use the same converter. I also added a Name
property and an IsFullyPopulated
property to the ChildObject
class.
Here are the DTO classes:
public abstract class BaseEntity
{
public string Id { get; set; }
}
public class ChildObject : BaseEntity
{
public string Name { get; set; }
public bool IsFullyPopulated { get; set; }
}
public class MyObject
{
[JsonProperty("ChildObject1")]
[JsonConverter(typeof(MyCustomObjectConverter))]
public ChildObject ChildObject1 { get; set; }
[JsonProperty("ChildObject2")]
[JsonConverter(typeof(MyCustomObjectConverter))]
public ChildObject ChildObject2 { get; set; }
[JsonProperty("ChildObject3")]
[JsonConverter(typeof(MyCustomObjectConverter))]
public ChildObject ChildObject3 { get; set; }
}
Here is the converter:
class MyCustomObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ChildObject));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
ChildObject child = null;
if (token.Type == JTokenType.String)
{
child = new ChildObject();
child.Id = token.ToString();
child.IsFullyPopulated = false;
}
else if (token.Type == JTokenType.Object)
{
child = token.ToObject<ChildObject>();
child.IsFullyPopulated = true;
}
else if (token.Type != JTokenType.Null)
{
throw new JsonSerializationException("Unexpected token: " + token.Type);
}
return child;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Here is the test program to demonstrate the operation of the converter:
class Program
{
static void Main(string[] args)
{
string json = @"
{
""ChildObject1"":
{
""Id"": ""key1"",
""Name"": ""Foo Bar Baz""
},
""ChildObject2"": ""key2""
}";
MyObject obj = JsonConvert.DeserializeObject<MyObject>(json);
DumpChildObject("ChildObject1", obj.ChildObject1);
DumpChildObject("ChildObject2", obj.ChildObject2);
DumpChildObject("ChildObject3", obj.ChildObject3);
}
static void DumpChildObject(string prop, ChildObject obj)
{
Console.WriteLine(prop);
if (obj != null)
{
Console.WriteLine(" Id: " + obj.Id);
Console.WriteLine(" Name: " + obj.Name);
Console.WriteLine(" IsFullyPopulated: " + obj.IsFullyPopulated);
}
else
{
Console.WriteLine(" (null)");
}
Console.WriteLine();
}
}
And here is the output of the above:
ChildObject1
Id: key1
Name: Foo Bar Baz
IsFullyPopulated: True
ChildObject2
Id: key2
Name:
IsFullyPopulated: False
ChildObject3
(null)