Reputation: 38417
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
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
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