C# – Serialize anonymous types with System.Text.Json

It’s common to need to customize serialization. When you need to do this, you’d typically need to create a custom JSON converter and pass it in during serialization. Depending on your scenario, an alternative approach is to use anonymous types, like this:

var json = JsonSerializer.Serialize(new { book.Title, book.Author });
Code language: C# (cs)

Basically you select properties from another object and format them as desired, and then serialize it. If you need to deserialize the JSON created from the anonymous type, you can deserialize to a dynamic object.

In this article, I’ll show a few cases where you could use the anonymous type approach to customize serialization. If you happen to find a new use case for this approach, feel free to leave a comment.

Use case 1 – Formatting before serializing

When you want to change the format of a property during serialization, normally you’d have to create a custom converter. In some cases, it may be simpler to select the property into an anonymous type, format it as desired, and serialize it.

Here’s an example. Let’s say you want to serialize a DateTime property and only want to show the time part of it. One way to do that is to convert the DateTime to a string with a time format, set this as a property in an anonymous type, and then serialize the anonymous type, like this:

var message = new Message()
{
	Text = "I want to go see the Old movie",
	From = "Mak",
	SentAt = DateTime.Now
};

var json = JsonSerializer.Serialize(new
{
	message.From,
	message.Text,
	SentAt = message.SentAt.ToString("hh:mm:ss")
}, new JsonSerializerOptions() { WriteIndented = true });

Console.WriteLine(json);
Code language: C# (cs)

This outputs the following JSON:

{
  "From": "Mak",
  "Text": "I want to go see the Old movie",
  "SentAt": "07:46:01"
}
Code language: JSON / JSON with Comments (json)

Read more about how you can change the date format used in JSON serialization.

Use case 2 – Serializing a subset of properties

Sometimes you’ll only want to serialize some of the properties instead of all of them. You can’t really use the [JsonIgnore] attribute in this scenario, because then the ignored properties would always be ignored. Instead, you can select the desired properties into an anonymous type and serialize it.

For example, let’s say you’re using the following logger that accepts an object parameter (for context purposes) that it’ll serialize into JSON:

public class JsonLogger
{
	public void Info(string message, object forContext);
}
Code language: C# (cs)

When you load a book from the database, you want to log that you loaded it, and you want to log the book title/author for context. To do that, you’d select the Title and Author properties into an anonymous type:

var book = GetBookFromDatabase(isbn: "9780679604181");
jsonLogger.Info("Loaded book from database", new { book.Title, book.Author });
Code language: C# (cs)

The logger serializes the context object into JSON and logs the following:

message=Loaded book from database context={"Title":"The Black Swan: The Impact of the Highly Improbable","Author":"Nassim Nicholas Taleb"}Code language: plaintext (plaintext)

Use case 3 – Changing property names to what the client expects

Let’s say your property names are in English and one of your clients expects the properties to be in Spanish.

You can’t use the JsonPropertyName attribute. That’d change the serialized property name every time you serialize. Instead you can:

  • Add properties to anonymous type using the names your client wants.
  • Map the regular object’s properties to the anonymous type properties.
  • Serialize the anonymous type.

Here’s an example:

var celebrity = new Celebrity()
{
	BirthDate = new DateTime(year: 1967, month: 2, day: 19),
	FirstName = "Benicio",
	LastName = "del Toro Sanchez"
};

var json = JsonSerializer.Serialize(new
{
	nombre = celebrity.FirstName,
	apellidos = celebrity.LastName,
	fechaDeNacimiento = celebrity.BirthDate.ToShortDateString()
	
}, new JsonSerializerOptions() { WriteIndented = true });

Console.WriteLine(json);
Code language: C# (cs)

This outputs the following:

{
  "nombre": "Benicio",
  "apellidos": "del Toro Sanchez",
  "fechaDeNacimiento": "2/19/1967"
}
Code language: JSON / JSON with Comments (json)

Note: This approach is known as the adapter pattern. It’s just not done in the traditional way where you add a new adapter class.

Note 2: In the Spanish naming system, there are two last names (apellidos). Usually only the first last name is shown. In the example above, ‘del Toro’ is the first last name, and he’s usually referred to as Benicio del Toro.

Use case 4 – Serializing internal properties

By default, JsonSerializer only serializes public properties. What if you want to serialize a non-public property without using a custom converter?

For example, let’s say you have the following class with an internal property called HappenedAt:

public class SystemEvent
{
	public string Name { get; set; }
	internal DateTimeOffset HappenedAt { get; set; }

	public SystemEvent()
	{
		HappenedAt = DateTimeOffset.Now;
	}
}
Code language: C# (cs)

To serialize the internal HappenedAt property, you could select its value into an anonymous type (assuming your code has access to the internal property):

var sysEvent = new SystemEvent()
{
	HappenedAt = DateTimeOffset.Now,
	Name = "Detected a disturbance in the force"
};

var json = JsonSerializer.Serialize(new
{
	sysEvent.Name,
	sysEvent.HappenedAt
}, new JsonSerializerOptions() { WriteIndented = true });

Console.WriteLine(json);
Code language: C# (cs)

If the code doesn’t have access to the internal property, one option is to get the property using reflection (with BindingFlags.NonPublic). Then put the value in an anonymous type and serialize it. Here’s an example:

var sysEvent = new SystemEvent()
{
	Name = "Detected a disturbance in the force"
};

var json = JsonSerializer.Serialize(new
{
	sysEvent.Name,
	HappenedAt = typeof(SystemEvent).GetProperty("HappenedAt", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(sysEvent)
}, new JsonSerializerOptions() { WriteIndented = true });

Console.WriteLine(json);
Code language: C# (cs)

Both approaches output the same JSON:

{
  "Name": "Detected a disturbance in the force",
  "HappenedAt": "2021-07-16T08:10:31.3865104-04:00"
}
Code language: JSON / JSON with Comments (json)

Comments are closed.