简体   繁体   中英

Using Newtonsoft.Json with nested custom classes

I need to serialize some custom objects in order to store information. However, I am struggling to deserialize those objects from the serialized JSON string back into their original object forms.

The serialized string seems fine:

[
  {
    "MyStringArray": [
      "stringInput1",
      "stringInput2"
    ],
    "MyCharArray": [
      "a",
      "b",
      "c",
      "."
    ],
    "MyString": "dummy",
    "MyClass3Object": [
      {
        "MyString": "ListInput1"
      },
      {
        "MyString": "ListInput2"
      }
    ]
  }
]

However, when I reconstruct the original MyClass1 object, the list has one entry as it should but it is filled with nulls instead of the corresnponding data. Any ideas as to what may be happening? Thanks in advance for the brainstorming :)

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.IO;
using System.Text.RegularExpressions;

namespace JsonTesting
{
  class Program
  {
    static void Main(string[] args)
    {

      MyClass1 c1 = new MyClass1();
      c1.AddInfo();

      string toJsonString = JsonConvert.SerializeObject(c1, Formatting.Indented,
        new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Include });
      File.WriteAllText(@"C:\temp\dumpJsonText.txt",toJsonString);
      MyClass1 fromJson = JsonConvert.DeserializeObject<MyClass1>(toJsonString);
      Console.ReadLine();

    }
  }


  public class MyClass1 : List<MyClass2> {
    public MyClass1() { }
    public void AddInfo() {
      this.Add(new MyClass2(new string[] { "stringInput1", "stringInput2" },
        new char[] { 'a', 'b', 'c', '.' },
        "dummy",
        new List<MyClass3>() { new MyClass3("ListInput1", new Regex(@"[A-Z]")), new MyClass3("ListInput2", new Regex(@"[0-9]")) }
        ));
    }
  }

  public class MyClass2
  {
    private string[] _myStringArray = null;
    private char[] _myCharArray = null;
    private string _myString = null;
    private List<MyClass3> _myClass3Object = null;

    public MyClass2() { }

    public MyClass2(string[] myStringArray, char[] myCharArray, string myString, List<MyClass3> myClass3Object)
    {
      _myStringArray = myStringArray;
      _myCharArray = myCharArray;
      _myString = myString;
      _myClass3Object = myClass3Object;
    }

    public string[] MyStringArray { get { return _myStringArray; } }
    public char[] MyCharArray { get { return _myCharArray; } }
    public string MyString { get { return _myString; } }
    public List<MyClass3> MyClass3Object { get { return _myClass3Object; } }

  }

  public class MyClass3 {

    private Regex _myRegex; 
    private string _myString = null;
    public MyClass3() { }
    public MyClass3(string myString, Regex myRegex) {

      _myString = myString;
      _myRegex = myRegex;
    }

    public string MyString{ get {return _myString;} }

  }

}

Your classes MyClass2 and MyClass3 are read-only. In order for Json.NET to deserialize a read-only type, you must either provide a custom JsonConverter that manually deserializes and constructs an instance of the type, or provide a parameterized constructor whose argument names match the property names modulo case . You have already created the necessary constructors and so are halfway done.

However your types have parameterless constructors as well. So, which constructor does Json.NET call? For a non-enumerable type that is serialized to a JSON object , the following rules apply:

  1. If [JsonConstructor] is set on a constructor, use that constructor.

  2. Next, in full trust only , when MemberSerialization.Fields is applied, or [Serializable] is applied and DefaultContractResolver.IgnoreSerializableAttribute == false , the special method FormatterServices.GetUninitializedObject() is used to allocate the object. None of the type's constructors are called.

    (This is an unusual case that does not arise often.)

  3. Next, if there is a public parameterless constructor, use it.

  4. Next, if a private parameterless constructor exists and the setting ConstructorHandling.AllowNonPublicDefaultConstructor is enabled, the private parameterless constructor is used.

  5. Next, if there is a single public parameterized constructor, use that constructor.

  6. Failing all of the above, Json.NET cannot construct instances of the type. An exception will get thrown during deserialization unless a custom converter is available.

Thus the parameterless constructors take precedence over the parameterized constructors. To force the parameterized constructors to be used, mark them with [JsonConstructor] as mentioned above:

public class MyClass3
{
    private Regex _myRegex;
    private string _myString = null;

    public MyClass3() { }

    [JsonConstructor]
    // The argument names must match the property names modulo case for Json.NET to deserialize the properties successfully.
    public MyClass3(string myString, Regex myRegex)
    {
        _myString = myString;
        _myRegex = myRegex;
    }

    public string MyString { get { return _myString; } }

    public Regex MyRegex { get { return _myRegex; } }
}

Alternatively, you could eliminate the parameterless constructor as it apparently did not exist in the first version of your question. Then make the same change to MyClass2 . Now your types will deserialize successfully.

Note that Json.NET has a built-in converter for serializing a Regex .

Sample fiddle .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM