System.Text.Json: Get the property name in a custom converter

Viewed 1931

Deserializing using JsonSerialize.DeserializeAsync and a custom converter, e.g.

public class MyStringJsonConverter : JsonConverter<string>
{
    public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return reader.GetString();
    }

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

Here I would get all string properties, which is kind of okay, though is there any way to check the property name for a given value, e.g. something like this, where to process only the Body property:

class MyMailContent 
{
    public string Name { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
}

public class MyStringJsonConverter : JsonConverter<string>
{
    public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.PropertyName.Equals("Body"))
        {
            var s = reader.GetString();
            //do some process with the string value
            return s;
        }

        return reader.GetString();
    }
}

Or is there some other way to single out a given property?

Note, I am looking for a solution using System.Text.Json.

2 Answers

System.Text.Json does not make the parent property name, or more generally the path to the current value, available inside JsonConverter<T>.Read(). This information is tracked internally -- it's in ReadStack.JsonPath() -- but ReadStack is internal and never passed to applications code.

However, as explained in Registration sample - [JsonConverter] on a property, you can apply your MyStringJsonConverter directly to public string Body { get; set; } by using JsonConverterAttribute:

class MyMailContent 
{
    public string Name { get; set; }
    public string Subject { get; set; }
    [JsonConverter(typeof(MyStringJsonConverter))]
    public string Body { get; set; }
}

By doing this, MyStringJsonConverter.Read() and .Write() will fire only for MyMailContent.Body. Even if you have some overall JsonConverter<string> in JsonSerializerOptions.Converters, the converter applied to the property will take precedence:

During serialization or deserialization, a converter is chosen for each JSON element in the following order, listed from highest priority to lowest:

  • [JsonConverter] applied to a property.
  • A converter added to the Converters collection.
  • [JsonConverter] applied to a custom value type or POCO.

(Note this differs partially from Newtonsoft where converters applied to types supersede converters in settings.)

Thanks to Jimi for the comment, and with his approval, I post a wiki answer using his suggested solution, and anyone who has more to contribute, please feel free to do so.

In most cases the accepted answer is a good one, though if one still need to process one or more specific properties by their property name, here is one way to do that.

public class MyMailContent
{
    public string Name { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
}

public class MyMailContentJsonConverter : JsonConverter<MyMailContent>
{
    public override MyMailContent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException();
        }

        var mailContent = new MyMailContent();

        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
            {
                return mailContent;
            }

            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                var propertyName = reader.GetString();

                reader.Read();

                switch (propertyName)
                {
                    case "Name":
                        mailContent.Name = reader.GetString();
                        break;

                    case "Subject":
                        mailContent.Subject = reader.GetString();
                        break;

                    case "Body":
                        string body = reader.GetString();
                        //do some process on body here
                        mailContent.Body = body;
                        break;
                }
            }
        }

        throw new JsonException();
    }

    public override void Write(Utf8JsonWriter writer, MyMailContent mailcontent, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

private static JsonSerializerOptions _jsonDeserializeOptions = new()
{
    ReadCommentHandling = JsonCommentHandling.Skip,
    AllowTrailingCommas = true,
    Converters =
    {
        new MyMailContentJsonConverter()
    }
};

And use it like this

var jsonstring = JsonSerializer.Serialize(new MyMailContent
{
    Name = "some name",
    Subject = "some subject",
    Body = "some body"
});

var MailContent = JsonSerializer.Deserialize<MyMailContent>(jsonstring, _jsonDeserializeOptions);
Related