How to Use Class Fields with System.Text.JSON.JSONserializer

How to use class fields with System.Text.Json.JsonSerializer?

In .NET Core 3.x, System.Text.Json does not serialize fields. From the docs:

Fields are not supported in System.Text.Json in .NET Core 3.1. Custom converters can provide this functionality.

In .NET 5 and later, public fields can be serialized by setting JsonSerializerOptions.IncludeFields to true or by marking the field to serialize with [JsonInclude]:

using System.Text.Json;

static void Main()
{
var car = new Car { Model = "Fit", Year = 2008 };

// Enable support
var options = new JsonSerializerOptions { IncludeFields = true };

// Pass "options"
var json = JsonSerializer.Serialize(car, options);

// Pass "options"
var carDeserialized = JsonSerializer.Deserialize<Car>(json, options);

Console.WriteLine(carDeserialized.Model); // Writes "Fit"
}

public class Car
{
public int Year { get; set; }
public string Model;
}

For details see:

  • How to serialize and deserialize (marshal and unmarshal) JSON in .NET: Include fields.

  • Issues #34558 and #876.

Serialize and Deserialize objects that contain complex properties using System.Text.Json

Finally was able to solve it using Newtonsoft instead of System.Text.Json.

I had to add some property that help me identify the class. So I created this enum:

public enum AnimalType
{
Dog,
Cat
}

Now my classes look like this:

public abstract class Animal
{
//[JsonConverter(typeof(CustomConverter))]
public abstract AnimalType AnimalType { get; }

public string? AnimalName { get; set; }
}

public class Dog : Animal
{
public override AnimalType AnimalType => AnimalType.Dog;

public string DogName { get; set; } = string.Empty;
}

public class Cat : Animal
{
public override AnimalType AnimalType => AnimalType.Cat;

public string CatName { get; set; } = string.Empty;
}

public class Person
{
public Animal Pet { get; set; }
}

And this converter enabled me to deserialize a Person:

public class AnimalConverter : JsonConverter<Animal>
{
public override Animal? ReadJson(JsonReader reader, Type objectType, Animal? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);

if (jObject is null)
return null;

var animalType = jObject.GetValue("AnimalType", StringComparison.OrdinalIgnoreCase)?.Value<string>();

if (animalType == null)
throw new Exception();

var animalTypeValue = Enum.Parse<AnimalType>(animalType);

switch (animalTypeValue)
{
case AnimalType.Dog:
return jObject.ToObject<Dog>();
case AnimalType.Cat:
return jObject.ToObject<Cat>();
default:
break;
}

throw new NotImplementedException();
}

public override bool CanWrite => false;

public override void WriteJson(JsonWriter writer, Animal? value, JsonSerializer serializer)
{
throw new Exception("This should never be called because CanWrite is set to false");
}
}

Make sure you specify to use that converter on your asp.net core web application as:

// use this converters
var mvcBuilder = builder.Services.AddControllers().AddNewtonsoftJson(c =>
{
c.SerializerSettings.Converters.Add(new StringEnumConverter());
c.SerializerSettings.Converters.Add(new AnimalConverter());
});

And use this NuGet packages:

    <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.3.0" />
</ItemGroup>

System.Text.Json won't deserialize to my type even though my constructor implements every property

I've had a look at your fiddle and spotted a couple of problems. Working fiddle here

  1. System.Text.Json is case-sensitive by default (except for web apps). You can resolve this by using either PropertyNamingPolicy = JsonNamingPolicy.CamelCase or PropertyNameCaseInsensitive = true in the serializer options.

  2. The second issue is outlined in Enums as strings

    By default, enums are serialized as numbers. To serialize enum names
    as strings, use the JsonStringEnumConverter.

    You should add JsonSerializerOptions to resolve (1) and (2):

    var options = new JsonSerializerOptions 
    {
    Converters =
    {
    new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
    },
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };
    var items = JsonSerializer.Deserialize<List<MenuItem>>(json, options);
  3. The third issue appears to be with the binding in the constructor
    for the list of Permissions. In the constructor you define a
    List<Permission> for the permissions parameter. I receive the
    error in your question unless the constructor argument type matches
    the model property type exactly. So, I updated the constructor to
    take a IReadOnlyList<Permission> and it deserializes successfully:

    [JsonConstructor]
    public MenuItem
    (
    string name,
    string url,
    IReadOnlyList<Permission> permissions
    )
    {
    this.Name = name;
    this.Url = url;
    this.Permissions = permissions ?? new List<Permission>().AsReadOnly();
    }

    Alternatively, you could change the Permissions property to List<Permission>.

This answer to a question with a similar problem explains that this is actually a limitation of System.Text.Json and there is currently an open github issue.

A working fork of your fiddle is demoed here.

Select type to use for deserialization in System.Text.Json

Thanks to @dbc for pointing me in the right direction, here's the converter I've ended up with:

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

using (var jsonDocument = JsonDocument.ParseValue(ref reader))
{
if (jsonDocument.RootElement.TryGetProperty(nameof(ScheduledJob.Schedule), out var typeProperty))
{
return JsonSerializer.Deserialize<ScheduledJob>(jsonObject, options);
}
else
{
return JsonSerializer.Deserialize<OnDemandJob>(jsonObject, options);
}
}
}

public override void Write(Utf8JsonWriter writer, JobBase value, JsonSerializerOptions options)
{
if(value is ScheduledJob)
{
JsonSerializer.Serialize(writer, value as ScheduledJob, options);
}
else
{
JsonSerializer.Serialize(writer, value as OnDemandJob, options);
}
}
}

How to sort serialized fields using System.Text.Json

After reading the thread stackoverflow.com/questions/3330989 indicated by @AlexeiLevenkov. I realized that It is easier using NewtonSoft.Json. So I set the attribute [JsonProperty(Order = -2)] to "ID" property and exchange System.Text.Json.JsonSerializer.Serialize(student) for JsonConvert.SerializeObject(student); It worked properly!

Below is the code updated.

class Program
{
static void Main(string[] args)
{
Student student = new Student()
{
Name = "Robert",
Id = Guid.NewGuid(),
LastName = "Alv",
Check = "Ok"
};

var resultJson = JsonConvert.SerializeObject(student);
Console.WriteLine(resultJson);
Console.ReadLine();

}
class BaseClass1
{
[JsonProperty(Order = -2)]
public Guid Id { get; set; }
}

class BaseClass2 : BaseClass1
{

}

class People : BaseClass2
{
public string Name { get; set; }
public string LastName { get; set; }
public string Check { get; set; }
}

class Student : BaseClass2
{
public string Name { get; set; }
public string LastName { get; set; }
public string Check { get; set; }
}
}

Can't deserialize JSON string by the help of System.Text.Json

By default, System.Text.Json doesn't support binding to fields. You could change Token in your model to a property:

[JsonPropertyName("token")]
public string Token { get; set; }

If you're using .NET 5 or above you could use [JsonInclude] attribute on public fields:

[JsonInclude]
[JsonPropertyName("token")]
public string Token;


Related Topics



Leave a reply



Submit