title: Suggestions for removing repeated code in custom JsonConverters
body: <p>I have quite a large and complex json input schema for one of my services. Because of this the solution involves around 50 custom JsonConverters to handle the different types, polymorphism etc. It all works but there's a considerable amount of repeated code in every converter, the only difference being setting up the local variables, handling the expected properties for that type and constructing it in the EndObject handler:</p>
  <pre class="lang-cs prettyprint-override"><code>public class Message
  {
      public int Id { get; set; }
      public DateTime SentAt { get; set; }
      public string MessageText { get; set; }
  
      public Message(int id, DateTime sentAt, string messageText)
      {
          Id = id;
          SentAt = sentAt;
          MessageText = messageText;
      }
  }
  
  internal class ExampleConverter : JsonConverter&lt;Message&gt;
  {
      public override Message Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
      {
          if (reader.TokenType != JsonTokenType.StartObject)
              throw new JsonException(&quot;Expected StartObject token&quot;);
  
          var id = Option.None&lt;int&gt;();
          var sentAt = Option.None&lt;DateTime&gt;();
          var messageText = Option.None&lt;string&gt;();
  
          try 
          {
              while (reader.Read())
              {
                  switch (reader.TokenType)
                  {
                      case JsonTokenType.EndObject:
                          return new Message(id, sentAt, messageText);
                      case JsonTokenType.PropertyName:
                          var propName = reader.GetString();
                          reader.Read();
  
                          switch(propName)
                          {
                              case nameof(Message.Id):
                                  id = Option.Some(int.Parse(reader.GetString() ?? string.Empty));
                                  break;
                              case nameof(Message.SentAt):
                                  sentAt = Option.Some(reader.GetDateTime());
                                  break;
                              case nameof(Message.MessageText):
                                  messageText = Option.Some(reader.GetString()?.ToUpper());
                                  break;
                          }
                          break;
                  }
              }
          }
          catch (OptionValueMissingException e)
          {
              throw new Exception(
                  $&quot;Missing property of type {e.OptionValueType}&quot;, e);
          }
          catch (JsonException e)
          {
              throw new Exception(
                  $&quot;Corrupted json&quot;, e);
          }
          catch (InvalidOperationException e)
          {
              throw new Exception(
                  $&quot;Unexpected type found&quot;, e);
          }
          throw new JsonException(&quot;Expected EndObject token&quot;);
      }
  
      public override void Write(Utf8JsonWriter writer, Message value, JsonSerializerOptions options)
      {
          throw new NotImplementedException();
      }
  }
  </code></pre>
  <p>I was hoping I could create <code>JsonConverterEx&lt;T&gt;</code> derived from <code>JsonConverter&lt;T&gt;</code> and provide my own <code>ReadTypeFromJson</code> method which takes functions to handle the EndObject and PropertyName type, something like this:</p>
  <pre class="lang-cs prettyprint-override"><code>public abstract class JsonConverterEx&lt;T&gt; : JsonConverter&lt;T&gt;
  {
      protected T ReadTypeFromJson(Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, Func&lt;T&gt; handleJsonEndObject, Action&lt;string&gt; handleJsonPropertyName)
      {
          if (reader.TokenType != JsonTokenType.StartObject)
              throw new JsonException(&quot;Expected StartObject token&quot;);
          
          try 
          {
              while (reader.Read())
              {
                  switch (reader.TokenType)
                  {
                      case JsonTokenType.EndObject:
                          return handleJsonEndObject();
                      case JsonTokenType.PropertyName:
                          var propName = reader.GetString();
                          reader.Read();
                          handleJsonPropertyName(propName);
                          break;
                  }
              }
          }
          catch (OptionValueMissingException e)
          {
              throw new Exception(
                  $&quot;Missing property of type {e.OptionValueType}&quot;, e);
          }
          catch (JsonException e)
          {
              throw new Exception(
                  $&quot;Corrupted json&quot;, e);
          }
          catch (InvalidOperationException e)
          {
              throw new Exception(
                  $&quot;Unexpected type found&quot;, e);
          }
          throw new JsonException(&quot;Expected EndObject token&quot;);
      }
  }
  </code></pre>
  <p>and then use it like this:</p>
  <pre class="lang-cs prettyprint-override"><code>internal class ExampleConverter : JsonConverterEx&lt;Message&gt;
  {
      public override Message Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
      {
          if (reader.TokenType != JsonTokenType.StartObject)
              throw new JsonException(&quot;Expected StartObject token&quot;);
  
          var id = Option.None&lt;int&gt;();
          var sentAt = Option.None&lt;DateTime&gt;();
          var messageText = Option.None&lt;string&gt;();
  
          return ReadTypeFromJson(reader, typeToConvert, options,
              () =&gt; new Message(id, sentAt, messageText),
              (propertyName) =&gt;
              {
  
                  switch (propertyName)
                  {
                      case nameof(Message.Id):
                          id = Option.Some(int.Parse(reader.GetString() ?? string.Empty));
                          break;
                      case nameof(Message.SentAt):
                          sentAt = Option.Some(reader.GetDateTime());
                          break;
                      case nameof(Message.MessageText):
                          messageText = Option.Some(reader.GetString()?.ToUpper());
                          break;
                  }
              });
      }
      
      public override void Write(Utf8JsonWriter writer, Message value, JsonSerializerOptions options)
      {
          throw new NotImplementedException();
      }
  }
  
  </code></pre>
  <p>Which would then reduce the amount of repeated code in all of my converters but I can't do this because I can't reference the ByRef Utf8JsonReader <code>reader</code> in my <code>handleJsonPropertyName</code> lambda, the calls to <code>reader</code> in the lambda give me <code>Cannot use 'ref' parameter 'reader' inside lambda expression</code> and I can't pass it as a parameter to the lambda because it's <code>ByRef</code> and I get <code>Cannot use 'System.Text.Json.Utf8JsonReader' as a type argument</code>.</p>
  <p>I've read up on ByRef structs and understand <a href="https://stackoverflow.com/a/64247388/525138">why I can't do this</a> (so only including above to show 'what I've tried') but is there anything else I can do to avoid all this repeated boilerplate? I need to use Converters as my schema is too complicated for System.Text.Json to be able to work out without.</p>
  <p>TIA</p>
  <p>Rich</p>