Muhammad Rehan Saeed
Muhammad Rehan Saeed

Reputation: 38417

How to Omit Inherited Property From JSON.NET Serialization

If I have the following classes I want to serialize using JSON.NET:

[DataContract]
public class Thing
{
    [DataMember(Name = "@context")]
    public string Context => "http://schema.org"
}

[DataContract]
public class Organization : Thing
{
    [DataMember(Name = "address")]
    public Address Address { get; set; }

    ...
}

[DataContract]
public class Address : Thing
{
    ...
}

When I use JSON.NET to serialize an Organization I get:

{
  "@context": "http://schema.org",
  "address": {
    "@context": "http://schema.org",
    ...
  }
  ...
}

What is the most efficient way of ensuring that the @context property only appears in the top level Organization object and not in the Address object?

Upvotes: 4

Views: 966

Answers (2)

Muhammad Rehan Saeed
Muhammad Rehan Saeed

Reputation: 38417

While @DimitryEgorov's answer is probably the correct way to go, it uses reflection which makes it slow. In the solution below, I use StringBuilder to do a string replace on the final JSON.

private const string ContextPropertyJson = "\"@context\":\"http://schema.org\",";

public override string ToString() => RemoveAllButFirstContext(
    JsonConvert.SerializeObject(this, new JsonSerializerSettings));

private static string RemoveAllButFirstContext(string json)
{
    var stringBuilder = new StringBuilder(json);
    var startIndex = ContextPropertyJson.Length + 1;
    stringBuilder.Replace(
        ContextPropertyJson, 
        string.Empty, 
        startIndex, 
        stringBuilder.Length - startIndex - 1);
    return stringBuilder.ToString();
}

Upvotes: 0

Dmitry Egorov
Dmitry Egorov

Reputation: 9650

If Organization is the only top level descendant of Thing and also no fields of type Organization may appear in serialized objects, you may easily do this by defining ShouldSerializeContext in Thing as follows:

[DataContract]
public class Thing
{
    [DataMember(Name = "@context")]
    public string Context => "http://schema.org";
    public bool ShouldSerializeContext() { return this is Organization; }
}

Demo: https://dotnetfiddle.net/GjmfbA


If any of the Thing's descendants may act as the root object, you may need to implement a custom converter. In WriteJson method of this converter, you may filter properties to be serialized. To remove the Context property from all but the root object check writer.Path, which will be an empty string for the root object:

[DataContract]
[JsonConverter(typeof(NoContextConverter))]
public class Thing
{
    [DataMember(Name = "@context")]
    public string Context => "http://schema.org";
}

// ...............

public class NoContextConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var props = value.GetType().GetProperties()
            .Where(p => Attribute.IsDefined(p, typeof(DataMemberAttribute)))
            .ToList();
        if (writer.Path != "")
            props.RemoveAll(p => p.Name == "Context");

        writer.WriteStartObject();
        foreach (var prop in props)
        {
            writer.WritePropertyName(prop.GetCustomAttribute<DataMemberAttribute>().Name);
            serializer.Serialize(writer, prop.GetValue(value, null));
        }
        writer.WriteEndObject();
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(Thing).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Demo: https://dotnetfiddle.net/cIlXID

N.B. For some reason, dotnetfiddle.net does not allow to use DataContractAttribute and DataMemberAttribute from System.Runtime.Serialization so I had to comment out relevant lines in this demo.

Upvotes: 2

Related Questions