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
System.Text.Json
is case-sensitive by default (except for web apps). You can resolve this by using eitherPropertyNamingPolicy = JsonNamingPolicy.CamelCase
orPropertyNameCaseInsensitive = true
in the serializer options.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);The third issue appears to be with the binding in the constructor
for the list ofPermissions
. In the constructor you define aList<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 aIReadOnlyList<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 toList<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
Should Functions Return Null or an Empty Object
C# .Equals(), .Referenceequals() and == Operator
Kill Process Tree Programmatically in C#
Generate a PDF That Automatically Prints
Dictionary Returning a Default Value If the Key Does Not Exist
"Invalid JSON Primitive" in Ajax Processing
Deserialize JSON Array Stream One Item at a Time
C# - Get a List of Files Excluding Those That Are Hidden
Format an Excel Column (Or Cell) as Text in C#
How to Get the Assembly File Version
Apply Properties Values from One Object to Another of the Same Type Automatically
Best Way in .Net to Manage Queue of Tasks on a Separate (Single) Thread
The Difference Between Try/Catch/Throw and Try/Catch(E)/Throw E