5

I have the following type:

public class Product : Dictionary<string, object>
{
    [JsonInclude]
    public string ProductId { get; set; }

    public Product(string productId) : base()
    {
        ProductId = productId;
    }
}

When serialising using System.Text.Json it does not include the properties (ie ProductId). Adding or removing the [JsonInclude] does not seem to make any effect.

Test case:

[Fact]
public void SimpleTest()
{
    var p = new Product("ABC123");
    p["foo"] = "bar";
    var json = JsonSerializer.Serialize(p);
    Assert.Contains("productId", json, StringComparison.OrdinalIgnoreCase);
}

And output received:

{"foo":"bar"}

How do I make it include my custom properties on my type during serialisation? (note: don't care about deserialisation).

6
  • 1
    How would you expect this to output? Commented Sep 16, 2021 at 3:01
  • 1
    Yes, this is correct. System.Text.Json only serializes the dictionary keys and values not the c# properties, as 1) there might be a key with the same name as a property, and 2) You probably don't want the "standard" properties like Count and IsReadOnly to be serialized. I can't find anywhere in the MSFT docs where this is stated, however Newtonsoft is documented to behave this way as is DataContractJsonSerializer and JavaScriptSerializer. System.Text.Json seems to have followed precedent. Commented Sep 16, 2021 at 3:07
  • 1
    You might consider a different data model where Product doesn't inherit from Dictionary but instead has a [System.Text.Json.Serialization.JsonExtensionData] public Dictionary<string, object> Properties { get; set; } property. The [JsonExtensionData] attribute causes the dictionary properties to be included as part of the parent object when serializing. Commented Sep 16, 2021 at 3:10
  • Other than that you will need to write a custom JsonConverter. (According to the docs for JsonInclude When applied to a property, indicates that non-public getters and setters can be used for serialization and deserialization. So it isn't relevant here as ProductId already has public getters and setters.) Commented Sep 16, 2021 at 3:14
  • 1
    @dbc Actually the JsonExtensionData attribute might do the trick, I'll check it out, thanks. Submit it as an answer and I'll accept Commented Sep 16, 2021 at 3:17

1 Answer 1

3

System.Text.Json does not serialize dictionary properties. I can't find anywhere in the MSFT docs where this is stated, but System.Text.Json only serializes the dictionary keys and values. This can be confirmed from the reference source for DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>, which is the converter used for your type.

This is likely because:

  1. There might be a key with the same name as a property.
  2. App developers almost certainly don't want the "standard" dictionary properties like Count and IsReadOnly to be serialized.
  3. Earlier serializers behave the same way and System.Text.Json is following precedent. Newtonsoft is documented to only serialize dictionary keys and values. DataContractJsonSerializer (with UseSimpleDictionaryFormat = true) and JavaScriptSerializer do also.

As an alternative, you might consider a different data model where Product doesn't inherit from Dictionary but instead has a [JsonExtensionData] public Dictionary<string, object> Properties { get; set; } property:

public class Product 
{
    public string ProductId { get; set; }

    [System.Text.Json.Serialization.JsonExtensionData]
    public Dictionary<string, object> Properties { get; set; } = new ();

    public Product(string productId) : base()
    {
        ProductId = productId;
    }
}

The [JsonExtensionData] attribute causes the dictionary properties to be included as part of the parent object when serializing and deserializing.

Notes:

  • If the suggested alternative is not acceptable, you will need to write a custom JsonConverter to manually serialize the .NET properties and keys and values of your Product dictionary.

  • Adding [JsonInclude] to ProductId will not force it to get serialized. According to the docs for JsonInclude, When applied to a property, [it] indicates that non-public getters and setters can be used for serialization and deserialization. So it isn't relevant here as ProductId already has public getters and setters.

  • Newtonsoft also has a JsonExtensionData attribute (which does the same thing). If you are using both serializers, be careful to use the correct attribute.

  • In your question you state don't care about deserialisation but deserialization does work correctly with JsonExtensionData.

Demo fiddle here.

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.