19

I want to be able to exclude a property when serializing using System.Text.Json.JsonSerializer. I don't want to use a JsonIgnore attribute everywhere I want to do this. I would like to be able to define the properties I want to exclude during serialization only, via some kind of Fluent API, which currently does not exist.

The only option I was able to find is to define a JsonConverter and add it to the list of Converters on the JsonSerializerOptions that I pass to the Serialize() method like so:

var options = new JsonSerializerOptions();
options.Converters.Add(new BookConverter());
json = JsonSerializer.Serialize(book, options);

In the JsonConverter I would have to write the entire JSON representation myself using a Utf8JsonWriter, excluding the property I don't want to serialize. This is a lot of work to just be able to exclude a property. While the JsonConverter is a great extensibility feature from the .NET team, its just too low-level for my use case. Does anyone know of any other way to acheive the exclusion of the property without having to write out the JSON representation myself?

I don't want to have to do the following:

  • Use an attribute, or dynamically add an attribute at runtime
  • Change the access modifier of the property to something like private or protected
  • Use a 3rd party library, as my issue is solvable if I use Json.NET.

Example:

class Program
{
    void Main()
    {
        // We want to serialize Book but to ignore the Author property
        var book = new Book() { Id = 1, Name = "Calculus", Author = new Author() };

        var json = JsonSerializer.Serialize(book);
        // Default serialization, we get this:
        // json = { "Id": 1, "Name": "Calculus", "Author": {} }

        // Add our custom converter to options and pass it to the Serialize() method
        var options = new JsonSerializerOptions();
        options.Converters.Add(new BookConverter());
        json = JsonSerializer.Serialize(book, options);
        // I want to get this:
        // json = { Id: 1, Name: "Calculus" }
    }
}

public class Author { }

public class Book
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Author Author { get; set; }
}

public class BookConverter : JsonConverter<Book>
{
    public override Book Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // Use default implementation when deserializing (reading)
        return JsonSerializer.Deserialize<Book>(ref reader, options);
    }

    public override void Write(Utf8JsonWriter writer, Book value, JsonSerializerOptions options)
    {
        // Serializing. Here we have to write the JSON representation ourselves
        writer.WriteStartObject();

        writer.WriteNumber("Id", value.Id);
        writer.WriteString("Name", value.Name);
        // Don't write Author so we can exclude it

        writer.WriteEndObject();
    }
}
Tareq Imbasher
  • 287
  • 2
  • 11

5 Answers5

11

Option 1 - Cast to Interface

  1. Extract interface which describes structure of desired object.

    public interface IBook
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    
  2. Implement it on the original class class Book : IBook

  3. Use the follow overload of string Serialize(object value, Type inputType, JsonSerializerOptions options = null);

    json = JsonSerializer.Serialize(book, typeof(IBook), options);
    

    If you're serializing array of Books (plural), you'll need to pass typeof(IEnumerable<IBook>) as an argument.

Option 2 - Use AutoMapper

This is useful if you don't have access to the original Book class.

  1. Create LiteBook class:

    public class LiteBook
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    
  2. Create mapping configuration:

    var config = new MapperConfiguration(cfg => {
        cfg.CreateMap<Book, LiteBook>();
    });
    
  3. Map it and serialize

    json = JsonSerializer.Serialize(new Mapper(config).Map<LiteBook>(book), options)
    
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Konstantin Nikolskii
  • 1,075
  • 1
  • 12
  • 17
7

So I happened to stumble upon an article that demonstrates how to use the JsonDocument object in the new System.Text.Json namespace and it is the next best thing to a Fluent API. Here is how this question can be solved.

The BookConverter.Write() method:

public override void Write(Utf8JsonWriter writer, Book value, JsonSerializerOptions options)
{
    writer.WriteStartObject();

    using (JsonDocument document = JsonDocument.Parse(JsonSerializer.Serialize(value)))
    {
        foreach (var property in document.RootElement.EnumerateObject())
        {
            if (property.Name != "Author")
                property.WriteTo(writer);
        }
    }

    writer.WriteEndObject();
}
Tareq Imbasher
  • 287
  • 2
  • 11
  • 5
    this works but the performance is not so good if it should run fast. The `value` is serialized by the default way before being parsed into a `JsonDocument`, only then we can actually start serializing (with property ignoring supported) the value ourselves manually. So the execution cost is multiplied by almost 2-2.5 times. – King King Sep 27 '20 at 08:59
  • I think this is a very bad idea as you also need to handle all of the custom stuff which is normally handled by the serializer, such as ignoring property that use `[JsonIgnore]` or perhaps have had their output name changed with `[JsonPropertyName("OptProperty")], this will also bump into other custom serializers causing them not to work. – harvzor Jul 21 '23 at 12:13
5

.Net 7 made it possible to more flexibly and dynamically control which properties get serialized. See here for the official blog post:

https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-7/#example-conditional-serialization

sammy34
  • 5,312
  • 5
  • 29
  • 42
0

You can simply ignore a property like this:

public class Book
{
    public int Id { get; set; }
    public string Name { get; set; }
    [JsonIgnore]
    public Author Author { get; set; }
}

reference: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-ignore-properties?pivots=dotnet-6-0

Zan
  • 116
  • 2
  • 7
0

You can try to use the following method:

public static object ConvertToObjectWithoutListedProperties<T>(
                     this T objectToTransform,
                     string[] propertiesToIgnore)
{
    var type = objectToTransform.GetType();
    var returnClass = new ExpandoObject() as IDictionary<string, object>;
    foreach (var propertyInfo in type.GetProperties())
        if (!propertiesToIgnore.Contains(propertyInfo.Name))
            returnClass.Add(propertyInfo.Name,
                            propertyInfo.GetValue(objectToTransform));
    return returnClass;
}

Credits: "Remove the null property from object"

ShamilS
  • 1,410
  • 2
  • 20
  • 40