I have an API using the json.net serialization library. It uses $ref and $id fields for circular references. RestKit does not realize that these $ref fields are referring back to another object that has already been serialized.
Is there a way to tell RestKit to use these fields so empty objects are not created?
Here is an example of my json:
{
"$id":"1",
"WorkOrder":{
"$id":"2",
"Location":{
"$id":"3",
"Address":{
"$id":"4",
"Guid":"8086990f-13a0-4f93-8a9b-043ff247ae66"
},
"WorkOrders":[
{
"$ref":"2"
}
],
"Guid":"ae58698d-4fcf-4c31-82bf-529077b6d059"
},
"Appointments":[
{
"$ref":"1"
}
],
"Guid":"94140fc6-9885-4395-a79d-2b60452f2bf4",
},
"Calendar":{
"$id":"5",
"OwnerID":"1bbda60d-0bda-4b97-b6e5-24460106bc54",
"IsActive":true,
"Appointments":[
{
"$ref":"1"
}
],
"Guid":"e6c91678-290d-4d12-b52f-9f6ad36dd679",
},
"Guid":"731f20c6-6ecb-4515-ade3-df47bc929c86",
}
The answer lies not in RestKit, but in your JSON.Net Serialization. Here are the issues:
So, to accomplish this, we need to get the objects from our Service Layer over to our API layer, using WCF References, then create our own json serialization using only the primary key of the object as a reference and not the $id $ref crap that WCF uses.
so, here are the details.
First of all, you are getting your $ref $id because you are calling into a WCF Service who's classes are decorated with a [DataContract(IsReference=true)]
attribute. The JSON.Net serializer will read this attribute and create the $id, $ref references IF you have NOT also decorated your classes with a [JsonObject(IsReference=false)]
. You need the DataContract IsReference to get your WCF service to serialize your objects over to your ApiController. Your ApiController now wants to serialize the objects up to the client as Json... your objects therefore also need the Json(IsReference=false) to prevent to $id, $ref
So here is how the data model class definition now looks
[JsonObject(IsReference=false)]
[DataContract(IsReference=true)]
public class SomeClassToSerialize
DataContract is from the System.Runtime.Serialization lib and JsonObject is from the NewtonSoft.Json lib
Ok... this is halfway solved. At this point, we will get the objects from the service back to our api using WCF References. And we have told our API NOT to use references, so our API service will now crash with a stack overflow, as it will try to serialize the circular references.
The next step is to create a custom JsonConverter
that will render the circular references using only the primary key. In my example below, all my objects inherit from an EntityBase. And the primary key of all these is a Guid called Guid. So, here is the logic...
Keep track of all rendered objects in a HashSet. If the object has not been rendered, then loop through each property and render the object. If the object HAS been rendered, then only render the primary key, (Guid).
public class GuidRefJsonConverter : JsonConverter
{
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return true; } }
public override bool CanConvert(Type objectType)
{
return typeof(EntityBase).IsAssignableFrom(objectType);
}
private HashSet<EntityBase> serializedObjects = new HashSet<EntityBase>();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
EntityBase eb = (EntityBase)value;
JObject jo = new JObject();
jo.Add("Guid", eb.Guid.ToString());
if (serializedObjects.Add(eb))
{
foreach (PropertyInfo prop in value.GetType().GetProperties())
{
if (prop.GetCustomAttribute<JsonIgnoreAttribute>() != null) continue;
if (prop.CanRead)
{
object propVal = prop.GetValue(value);
if (propVal != null && prop.Name!="Guid")
{
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
}
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
lastly, we just need to register our JsonConverter. In the WebApiConfig.Register() method, just register the serializer.
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.Converters=new List<JsonConverter>(){new GuidRefJsonConverter()};
and that does it. The API will now serialize a circular reference with just the primary key, which RestKit will pick up as an empty Upsert, and will map all its pointers correctly.
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