Skip to content

System.Text.Json: How to serialize only certain properties without using JsonIgnoreAttribute #593

@epignosisx

Description

@epignosisx

I'm trying to migrate some of our Newtonsoft.Json functionality over to System.Text.Json and we used to have code that would opt-in to serialization instead of the opt-out model of the JsonIgnoreAttribute.

The scenario is in a web app that we like to log the request object when validation fails, but just certain properties to avoid any PII/PCI issues.

We can't use the JsonIgnoreAttribute for two reasons:

1- Adding the attribute will stop the serializer used by ASP.NET Core from deserializing the HTTP request into an object. We still want this. We want that when we serialize it with our own JsonSerializer we add our customization to only allow certain properties.

2- For security reasons we want to have an opt-in model instead of opt-out in case a new sensitive property is added but the attribute is forgotten. We want to avoid getting this data into our logs.

This is our code simplified:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class LogAttribute : Attribute
{
}

public class PersonApiRequest
{
    [Log]  // <-- serialization opt-in attribute
    public int Id { get; set; }

    [RegularExpression("SomeRegex")]
    public string Name { get; set; }
    
    [RegularExpression("SomeRegex")]
    public string Ssn { get; set; }
}

public class LogContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        var attributes = property.AttributeProvider.GetAttributes(typeof(LogAttribute), true);
        property.Ignored = attributes.Count == 0;
        return property;
    }
}

public class PersonController : Controller
{
    private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings { ContractResolver = new LogContractResolver() };
    
    [HttpPost]
    public IActionResult Index([FromBody]PersonApiRequest apiRequest)
    {
        if (!ModelState.IsValid)
        {
            var modelSerialized = JsonConvert.SerializeObject(apiRequest, SerializerSettings);
            _logger.LogWarning("Invalid {Request}", modelSerialized);
            return BadRequest(ModelState);
        }
        ...
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Text.Json

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions