Serialize an Entity Framework entity to JSON (Newtonsoft.Json)

Everytime when it comes to serializing an entity the navigation properties may lead to circular references and crashing the serialization process. Of course there’re configuration settings that can be adjusted but I wanted a solution for any developer consuming these entities should be able to json-serialize them.

Well there’re quite a lot arguments against serializing and using entities in a web service but also at least some exceptions for it.

In my solution I’m using a custom json converter which ignores all the navigation properties (virtual members of the entity) in the serialization process. So I came up with the following converter class:

public class EntityConverter : JsonConverter
{
    #region Overrides of JsonConverter

    /// <summary>Writes the JSON representation of the object.</summary>
    /// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
    /// <param name="value">The value.</param>
    /// <param name="serializer">The calling serializer.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // Serialize the entity by ignoring virtual members.
        var e = new ExpandoObject();
        var d = (IDictionary<string, object>)e;

        var pis = value.GetType().GetProperties();
        foreach (var pi in pis)
        {
            if (!pi.GetGetMethod().IsVirtual)
            {
                d[pi.Name] = pi.GetValue(value);
            }
        }
        var json = JsonConvert.SerializeObject(e);

        writer.WriteStartObject();
        writer.WriteRaw(json);
        writer.WriteEndObject();
    }

    /// <summary>Reads the JSON representation of the object.</summary>
    /// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
    /// <param name="objectType">Type of the object.</param>
    /// <param name="existingValue">The existing value of object being read.</param>
    /// <param name="serializer">The calling serializer.</param>
    /// <returns>The object value.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("No custom read (CanRead property is false). Use default read process.");
    }

    /// <summary>
    /// Determines whether this instance can convert the specified object type.
    /// </summary>
    /// <param name="objectType">Type of the object.</param>
    /// <returns>
    ///     <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
    /// </returns>
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsClass;
    }

    /// <summary>
    /// Gets a value indicating whether this <see cref="T:Newtonsoft.Json.JsonConverter" /> can read JSON.
    /// </summary>
    /// <value><c>true</c> if this <see cref="T:Newtonsoft.Json.JsonConverter" /> can read JSON; otherwise, <c>false</c>.</value>
    public override bool CanRead => false;

    #endregion
}

 

The WriteJson method contains the custom serialization of the specified object which should be serialized. I use an ExpandoObject to add the properties at runtime. Find more about this dynamic class here. After adding all the non-virtual members to the dynamic instance (represented by a dictionary) I use the JsonConvert.SerializeObject method for serialization. By passing the JSON string to the writer’s WriteRaw method the JSON is used as the result of the custom serialization.

Note: If you wrap the serialized entity in an other JSON string make sure you trim the leading and closing braces {} from the entity json by changing the following line:

// OLD
writer.WriteRaw(json);

// NEW
writer.WriteRaw(json.TrimStart('{').TrimEnd('}'));

 

Now we only have to add the JsonConverterAttribute to our generated entities. To achieve this and avoid overwriting your changes with every single generation of your entities (e.g. on updating your model from the database) we have to customize the T4 template. Expand your .edmx (e.g. MyTest.edmx) file in Visual Studio and double click on the .tt file (e.g. MyTest.tt – NOT the MyTest.Context.tt).

The T4 template opens in the Visual Studio editor. Now search for the term: public string EntityClassOpening(EntityType entity)

This method provides the String which is used to create the class definition line for each entity. Add the JsonConverter attribute right before the class definition:

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "[Newtonsoft.Json.JsonConverter(typeof(NamespaceToMyCustomConverter.EntityConverter))]\r\n{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
}

 

That’s it. Saving the T4 templates leads to a regeneration of the entity classes. You’re going to find the JsonConverter attribute in each of your entity classes. Now you’re able to serialize an entity using Newtonsoft.Json without changing the serialization configuration.

 

Share

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*