Search code examples
c#jsonjson-deserializationsystem.text.json

Deserializing a json array into a object containing List


I found this question/answer Deserializing a json array into a object C# using System.Text.Json. But the answer doesn't fully work for me. The issue is that once I try to access the list from inside class Bar, data is null.

JSON :

[
    {
        "param1":"value",
        "param2":"value2"
    },
    {
        "param1":"value",
        "param2":"value2"
    }
]

Class objects:

public class Bar : List<Foo>
  {
    public List<Foo> data { get; set; }

    public override string ToString()
    {
      StringBuilder sb = new StringBuilder();
      foreach( Foo item in data )
      {
        sb.Append( item.ToString() );
      }

      return sb.ToString();
    }
  }
  public class Foo
  {
    public string param1 { get; set; }
    public string param2 { get; set; }

    public override string ToString()
    {
      StringBuilder sb = new StringBuilder();
      sb.Append( " param 1 : " +  param1 + Environment.NewLine );
      sb.Append( " param 2 : " + param2 + Environment.NewLine );

      return sb.ToString();
    }
  }

Calling it:

string? director = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
string configfile = String.Empty;

if ( string.IsNullOrEmpty( director ) == false )
{
  configfile = Path.Combine( director, "ArrrayTest.txt" );
  if ( ( string.IsNullOrEmpty( configfile ) == false ) && ( File.Exists( configfile ) == true ) )
  {
    using ( StreamReader sr = File.OpenText( configfile ) )
    {
      string jsonstring = sr.ReadToEnd();
      Bar? output = JsonSerializer.Deserialize<Bar>( jsonstring );
      if ( output != null )
      { 
        Console.WriteLine(output.ToString() );
      }
    } 
  }

At the line if ( output != null ) output is not null and data contains two elements. But inside output.ToString(); data is null.

How do I make this work, so that inside the Bar class, data isn't null? Why would data be different when accessed from outside, then when accessed from inside the object?

I'm using VS 2022, .NET 6 and System.Text.json.


Solution

  • The issue is that your Bar class is both a List<Foo> and contains a property List<Foo> data. This design is confusing because it's unclear where the JSON array should be deserialized to – the Bar object itself (since it's a List<Foo>) or the data property inside it.

    When you call JsonSerializer.Deserialize<Bar>(jsonstring);, the serializer tries to match the JSON array to the Bar class. Since Bar is a List<Foo>, it successfully populates the Bar instance itself with the Foo items from the array. However, the data property remains null because there's no corresponding property in the JSON array to map it to.

    The solution is to clarify the design. If Bar should contain the list, then it shouldn't inherit from List<Foo>. If Bar should inherit from List<Foo>, then you don't need the data property.

    Here's how you can adjust your Bar class:

    Option 1: Remove inheritance and use the data property only.

    public class Bar
    {
      // No longer inheriting from List<Foo>
      public List<Foo> data { get; set; } = new List<Foo>();  // Initialize so it's never null
    
      public override string ToString()
      {
        StringBuilder sb = new StringBuilder();
        foreach (Foo item in data)  // Now it uses the 'data' property
        {
          sb.Append(item.ToString());
        }
        return sb.ToString();
      }
    }
    

    With this option, you don't have to change your deserialization code. The JSON array will correctly populate the data property.

    Option 2: Remove the data property and use the Bar class itself.

    public class Bar : List<Foo>
    {
      public override string ToString()
      {
        StringBuilder sb = new StringBuilder();
        foreach (Foo item in this)  // 'this' refers to the Bar itself as it's a List<Foo>
        {
          sb.Append(item.ToString());
        }
        return sb.ToString();
      }
    }
    

    For option 2, your calling code should work as it is, since Bar itself is the list, and all items in the deserialized JSON array are placed in Bar directly.

    After adjusting your Bar class according to one of these options, the data should not be null when accessed within the Bar class' ToString() method. The confusion arises from trying to use both inheritance and a property to represent the same data.

    Remember to update all other parts in your code that might depend on Bar being a List<Foo> or having a data property to match your chosen design.