skip to Main Content

I am using .NET 8 and try to deserialize a class with a [JsonConstructor] that has a Dictionary<string,int> as parameter.
But this always fails with this error:

Reference metadata is not supported when deserializing constructor parameters.

I think this is because of the reference id "$id" in the JSON:

  "MyClass": {
    "$id": "3",
    "$type": "MyClassType",
    "MyDictionary": {
      "$id": "4",
      "aaaa": 5661,
      "bbbbb": 5661
    }
  }

This is my class:

public Dictionary<string, int> MyDictionary{ get; set; } = new();

[JsonConstructor]
private MyClass(Dictionary<string, int> MyDictionary)
{
    this.MyDictionary= MyDictionary;
    //...doing some other setup stuff
}

Is there any way to handle this without do write a custom converter?

If I do not use:

ReferenceHandler = ReferenceHandler.Preserve

this part works, but for my project I need to keep references.

2

Answers


  1. You were on the right track in that $ represents metadata, not an actual data field. But the fix is ​​actually to do this:

    JsonSerializerSettings settings = new JsonSerializerSettings
    {
        MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
    };
    

    Set the metadata handling to ignore, and then you can serialize/deserialize the property using the PropertyName attribute:

    [JsonProperty(PropertyName = "$id")]
    public string Id { get; set; }
    

    This is the sample code:

    internal class Program
    {
        static void Main(string[] args)
        {
            var str = @"{
                '$id': '3',
                '$type': 'MyClassType',
                'MyDictionary': {
                  '$id': 4,
                  'aaaa': 5661,
                  'bbbbb': 5661
                }
            }";
    
            JsonSerializerSettings settings = new JsonSerializerSettings
            {
                MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            };
            var example = JsonConvert.DeserializeObject<MyClass>(str, settings);
            Console.ReadLine();
        }
    }
    
    
    public class MyClass
    {
        [JsonProperty(PropertyName = "$id")]
        public string Id { get; set; }
    
        [JsonProperty(PropertyName = "$type")]
        public string Type { get; set; }
    
        public Dictionary<string, int> MyDictionary { get; set; }
    
        [JsonConstructor]
        public MyClass(string id, string type, Dictionary<string, int> dictionary)
        {
            Id = id;
            Type = type;
            MyDictionary = dictionary;
        }
    
    }
    
    Login or Signup to reply.
  2. The exception error message is self-explanatory: ReferenceHandler.Preserve does not work for types with parameterized constructors. System.Text.Json will throw an exception if you try to use it with a type with a parameterized constructor.

    I cannot find any place in the documentation where this is stated precisely, however in Preserve references and handle circular references MSFT writes:

    This feature can’t be used to preserve value types or immutable types. On deserialization, the instance of an immutable type is created after the entire payload is read. So it would be impossible to deserialize the same instance if a reference to it appears within the JSON payload.

    For value types, immutable types, and arrays, no reference metadata is serialized. On deserialization, an exception is thrown if $ref or $id is found. However, value types ignore $id (and $values in the case of collections) to make it possible to deserialize payloads that were serialized by using Newtonsoft.Json. …

    While MSFT does not state this explicitly, it seems that any type with a parameterized constructor is considered to be immutable for the purposes of this limitation.

    As a workaround, you will need to modify your class to have a parameterless constructor. Since your type is not in fact immutable (as your MyDictionary property has a setter), you could do so by moving the doing some other setup stuff setup logic into an IJsonOnDeserialized.OnDeserialized callback, e.g. like so:

    public class MyClass : IJsonOnDeserialized
    {
        public Dictionary<string, int> MyDictionary { get; set; }
    
        public MyClass() : this(new()) { }
    
        public MyClass(Dictionary<string, int> MyDictionary)
        {
            this.MyDictionary= MyDictionary;
            DoSetup();
        }       
    
        private void DoSetup()
        {
            //...doing some other setup stuff
            // Since this is called after deserialization you can use the populated dictionary in your setup, e.g. like so:
            InitialDictionaryCount = MyDictionary.Count;
        }
    
        void IJsonOnDeserialized.OnDeserialized() => DoSetup();
        
        [JsonIgnore]
        public int InitialDictionaryCount { get; private set; }
    }
    

    Since OnDeserialized() will be called after the MyClass instance is completely populated, you will be able to use MyDictionary and any other needed properties for your setup.

    Demo fiddle here.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search