1

I have the following model structure:

public class A
{
    [JsonProperty("id")]
    public string Id { get { return "A:ID"; } }
    
    [JsonProperty("CompKey")]
    public string CompKey { get; set; }
}

public class B : A
{
    [JsonProperty("id")]
    public string Id { get { return "B:ID"; } }
    
    [JsonProperty("name")]
    public string Name { get; set; }
}

I want to serialize an instance of class B but only want the Id property of class A to be visible. I created a contract resolver to pass to Newtonsoft:

public class DatabaseEntryResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        property.ShouldSerialize = instance => property.DeclaringType == typeof(A);
        return property;
    }
}

I use this in the following manner:

var someObject = new A();
var json = JsonConvert.SerializeObject(someObject, new JsonSerializerSettings
{
    ContractResolver = new DatabaseEntryResolver()
});

However, neither of the ID properties are being created in the JSON structure. Anyone might shed some insight on this issue I am having?

1
  • Why var someObject = new A(); instead of var someObject = new B();? Isn't the issue that you need to get A.Id when you serialize an instance of B? Commented Jun 10, 2021 at 20:52

2 Answers 2

4

Json.NET will never serialize two properties with the same name. If DefaultContractResolver encounters two identically named properties, it chooses to serialize the most derived property that is not ignored. Notably, this decision is based on static metadata such as the presence of absence of [JsonIgnore] -- and not on runtime state such as the return from ShouldSerialize.

Thus, to make A.Id supersede B.Id when serializing an instance of B, you must override CreateProperty() as follows:

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
    var property = base.CreateProperty(member, memberSerialization);
    if (member.ReflectedType == typeof(B)) // Use (typeof(A).IsAssignableFrom(member.ReflectedType)) if you want this to apply to all subclasses of A
    {
        // Make A.Id supersede B.Id
        if (member.Name == nameof(A.Id) && member.DeclaringType != typeof(A))
            property.Ignored = true;
    }
    return property;
}

In addition, if you want really ony want the Id property of class A to be visible when serializing an instance of B, you must in addition remove or ignore everything else in CreateProperties():

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
    var properties = base.CreateProperties(type, memberSerialization);
    if (type == typeof(B)) // Use typeof(A).IsAssignableFrom(type)) if you want to only serialize the Id property of A and all its subclasses
    {
        // Remove everything except Id.  You could also Ignore everything except Id.
        properties = properties.Where(p => p.UnderlyingName == nameof(A.Id)).ToList();
    }
    return properties;
}

Notes:

  • Presumably Newtonsoft does not check ShouldSerialize to determine which Id property to bind to JSON because ShouldSerialize cannot be called during deserialization.

  • If you really just want to serialize { "id": "A:ID" } for every instance of A or any subclass of A, use typeof(A).IsAssignableFrom() where I mention it in code comments.

  • You should mark B.Id with the new modifier to avoid a compilation warning.

    public class B : A
    {
        [JsonProperty("id")]
        public new string Id { get { return "B:ID"; } } // Fixed compilation
    

    For more see Knowing When to Use Override and New Keywords (C# Programming Guide).

Demo fiddle here;

Sign up to request clarification or add additional context in comments.

3 Comments

Very well explained. This solution might work. Got it to work by following what the guys over at Json.Net suggested: github.com/JamesNK/Newtonsoft.Json/issues/…
Simply marking B.Id with [JsonIgnore] will work if you never want to serialize B.Id. I inferred you were using a contract resolver so you could optionally serialize one or the other depending on your needs at runtime.
No, actually that is what I perfectly want to do, sometimes I might need to serialize A.Id and sometimes B.Id. After looking at your code now, I can say that it is exactly what the guys at Json.Net proposed, however, their approach is simpler as it just overloads another method that is supposed to do the stuff you outlined in your response.
0

For whoever has encountered this issue, an explanation was given by the guys who developer Netwonsoft.JSON here, such that my new contract resolver is the following:

public class DatabaseEntryResolver : DefaultContractResolver
{
    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        return base.GetSerializableMembers(objectType).Where(member => member.DeclaringType == typeof(A);
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        property.ShouldSerialize = instance => property.DeclaringType == typeof(A);
        return property;
    }
}

Note how I still decided to leave the overloaded CreateProperty method, even though technically it is not needed as the overloaded GetSerializableMembers method already filters out the properties I need. I decided to this just in case but without it the solution still works.

1 Comment

While this may do what you need, it doesn't do exactly what you wrote in your question. Your question states I want to serialize an instance of class B but only want the Id property of class A to be visible. but what this does is to serialize an instance of B as if it were an instance of A. Which is a perfectly fine thing to want to do, just slightly different than stated.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.