title: System.Text.Json fails to deserialize List<List<object>>
body: <p>I'm using <code>System.Text.Json</code> and it fails to deserialize <code>BookLevel[]</code>. BookLevel is something like <code>List&lt;List&lt;object&gt;&gt;</code>.</p>
  <blockquote>
  <p>The JSON value could not be converted to Deribit.Models.BookLevel. Path: $.params.data.bids[0] | LineNumber: 0 | BytePositionInLine: 234.. Exception: JsonException</p>
  </blockquote>
  <pre class="lang-cs prettyprint-override"><code>public record BookResponse
  {
      [JsonPropertyName(&quot;type&quot;)]
      public string Type { get; init; } = null!;
  
      [JsonPropertyName(&quot;timestamp&quot;)]
      public long Timestamp { get; init; }
  
      [JsonPropertyName(&quot;prev_change_id&quot;)]
      public decimal PreviousChangeId { get; init; }
  
      [JsonPropertyName(&quot;instrument_name&quot;)]
      public string InstrumentName { get; init; } = null!;
  
      [JsonPropertyName(&quot;change_id&quot;)]
      public decimal ChangeId { get; init; }
  
      [JsonPropertyName(&quot;bids&quot;)]
      public BookLevel[] Bids { get; init; } = null!;
  
      [JsonPropertyName(&quot;asks&quot;)]
      public BookLevel[] Asks { get; init; } = null!;
  }
  
  public record BookLevel
  {
      [JsonPropertyOrder(1)]
      public string Action { get; init; } = null!;
  
      [JsonPropertyOrder(2)]
      public decimal Amount { get; init; }
  
      [JsonPropertyOrder(3)]
      public decimal Price { get; init; }
  }
  
  </code></pre>
  <pre><code>{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;method&quot;:&quot;subscription&quot;,&quot;params&quot;:{&quot;channel&quot;:&quot;book.BTC-PERPETUAL.100ms&quot;,&quot;data&quot;:{&quot;type&quot;:&quot;change&quot;,&quot;timestamp&quot;:1648477437698,&quot;prev_change_id&quot;:42599922395,&quot;instrument_name&quot;:&quot;BTC-PERPETUAL&quot;,&quot;change_id&quot;:42599922580,&quot;bids&quot;:[[&quot;change&quot;,47452.0,55700.0],[&quot;change&quot;,47451.5,24170.0],[&quot;delete&quot;,47449.0,0.0],[&quot;new&quot;,47446.5,2130.0],[&quot;change&quot;,47440.5,56210.0],[&quot;new&quot;,47439.0,46520.0],[&quot;new&quot;,47438.0,660.0],[&quot;new&quot;,47437.0,47430.0],[&quot;change&quot;,47429.5,20000.0],[&quot;change&quot;,47429.0,2810.0],[&quot;change&quot;,47428.5,36460.0],[&quot;change&quot;,47428.0,3070.0],[&quot;new&quot;,47427.0,21110.0],[&quot;delete&quot;,47423.5,0.0],[&quot;new&quot;,47421.0,33400.0],[&quot;change&quot;,47420.5,33190.0],[&quot;new&quot;,47420.0,140.0],[&quot;change&quot;,47390.0,63980.0],[&quot;new&quot;,47382.0,85480.0],[&quot;delete&quot;,47381.0,0.0],[&quot;new&quot;,47379.5,32770.0]],&quot;asks&quot;:[[&quot;change&quot;,47452.5,15950.0],[&quot;new&quot;,47467.0,101970.0],[&quot;delete&quot;,47467.5,0.0],[&quot;change&quot;,47469.0,1200.0],[&quot;change&quot;,47470.5,31470.0],[&quot;change&quot;,47471.5,2010.0],[&quot;change&quot;,47474.0,79380.0],[&quot;change&quot;,47474.5,47470.0],[&quot;new&quot;,47475.5,2970.0],[&quot;new&quot;,47476.0,21010.0],[&quot;change&quot;,47476.5,7630.0],[&quot;change&quot;,47477.0,42510.0],[&quot;change&quot;,47478.5,100.0],[&quot;delete&quot;,47480.0,0.0],[&quot;change&quot;,47482.5,5650.0],[&quot;delete&quot;,47485.5,0.0],[&quot;new&quot;,47494.0,150.0],[&quot;change&quot;,47494.5,43340.0],[&quot;new&quot;,47523.5,32590.0],[&quot;delete&quot;,47527.5,0.0]]}}}
  </code></pre>
  <h2>Back in Newtonsoft.Json</h2>
  <p>I could've marked <code>BookLevel</code> as <code>[JsonConverter(typeof(ObjectToArrayConverter&lt;LevelEvent&gt;))]</code> and use the following. How do I do that with System.Text.Json?</p>
  <pre class="lang-cs prettyprint-override"><code>/// &lt;summary&gt;
  /// Adapted from https://stackoverflow.com/questions/39461518/c-sharp-json-net-deserialize-response-that-uses-an-unusual-data-structure
  /// &lt;/summary&gt;
  /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;
  public class ObjectToArrayConverter&lt;T&gt; : JsonConverter
  {
      public override bool CanConvert(Type objectType)
      {
          return typeof(T) == objectType;
      }
  
      static bool ShouldSkip(JsonProperty property)
      {
          return property.Ignored || !property.Readable || !property.Writable;
      }
  
      public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
      {
          var type = value.GetType();
  
          if (!(serializer.ContractResolver.ResolveContract(type) is JsonObjectContract contract))
          {
              throw new JsonSerializationException(&quot;invalid type &quot; + type.FullName);
          }
      
          var list = contract.Properties.Where(p =&gt; !ShouldSkip(p)).Select(p =&gt; p.ValueProvider.GetValue(value));
          serializer.Serialize(writer, list);
      }
  
      public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
      {
          if (reader.TokenType == JsonToken.Null)
          {
              return null;
          }
      
          var token = JArray.Load(reader);
  
          if (!(serializer.ContractResolver.ResolveContract(objectType) is JsonObjectContract contract))
          {
              throw new JsonSerializationException(&quot;invalid type &quot; + objectType.FullName);
          }
      
          var value = existingValue ?? contract.DefaultCreator();
      
          foreach (var pair in contract.Properties.Where(p =&gt; !ShouldSkip(p)).Zip(token, (p, v) =&gt; new { Value = v, Property = p }))
          {
              var propertyValue = pair.Value.ToObject(pair.Property.PropertyType, serializer);
              pair.Property.ValueProvider.SetValue(value, propertyValue);
          }
  
          return value;
      }
  }
  </code></pre>