System.Text.Json API is there something like IContractResolver
The equivalent types in System.Text.Json --
JsonClassInfo
andJsonPropertyInfo
-- are internal. There is an open enhancement
Equivalent of DefaultContractResolver in System.Text.Json #31257
asking for a public equivalent. – dbc Nov 25 at 19:11
Github issues:
- Open up metadata infrastructure of System.Text.Json #34456
- Equivalent of DefaultContractResolver.CreateProperties override in System.Text.Json #60518
- Equivalent of DefaultContractResolver in System.Text.Json #31257
Please try this:
I wrote this as an extension to System.Text.Json to offer missing features: https://github.com/dahomey-technologies/Dahomey.Json.
You will find support for programmatic object mapping.
Define your own implementation of IObjectMappingConvention:
public class SelectiveSerializer : IObjectMappingConvention
{
private readonly IObjectMappingConvention defaultObjectMappingConvention = new DefaultObjectMappingConvention();
private readonly string[] fields;
public SelectiveSerializer(string fields)
{
var fieldColl = fields.Split(',');
this.fields = fieldColl
.Select(f => f.ToLower().Trim())
.ToArray();
}
public void Apply<T>(JsonSerializerOptions options, ObjectMapping<T> objectMapping) where T : class
{
defaultObjectMappingConvention.Apply<T>(options, objectMapping);
foreach (IMemberMapping memberMapping in objectMapping.MemberMappings)
{
if (memberMapping is MemberMapping<T> member)
{
member.SetShouldSerializeMethod(o => fields.Contains(member.MemberName.ToLower()));
}
}
}
}
Define your class:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
Setup json extensions by calling on JsonSerializerOptions the extension method SetupExtensions defined in the namespace Dahomey.Json:
JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
Register the new object mapping convention for the class:
options.GetObjectMappingConventionRegistry().RegisterConvention(
typeof(Employee), new SelectiveSerializer("FirstName,Email,Id"));
Then serialize your class with the regular Sytem.Text.Json API:
Employee employee = new Employee
{
Id = 12,
FirstName = "John",
LastName = "Doe",
Email = "john.doe@acme.com"
};
string json = JsonSerializer.Serialize(employee, options);
// {"Id":12,"FirstName":"John","Email":"john.doe@acme.com"};
How do I add lexicographical sort in this System.Text.Json serialization way?
As explained in Configure the order of serialized properties, as of .NET 6 the only mechanism that System.Text.Json provides to control property order during serialization is to apply [JsonPropertyOrder]
attributes to your model [1]. You have already done so -- but the order specified is not lexicographic. So what are your options?
Firstly, you could modify your model so that the [JsonPropertyOrder]
are in lexicographic order:
internal class MexcSubPersonalPayload
{
private readonly string _apiSecret;
public MexcSubPersonalPayload(string apiKey, string apiSecret)
{
ApiKey = apiKey;
_apiSecret = apiSecret;
}
[JsonPropertyName("op")]
[JsonPropertyOrder(2)]
public string Operation => "sub.personal";
[JsonPropertyName("api_key")]
[JsonPropertyOrder(1)]
public string ApiKey { get; }
[JsonPropertyName("sign")]
[JsonPropertyOrder(4)]
public string Signature => Sign(ToString());
[JsonPropertyName("req_time")]
[JsonPropertyOrder(3)]
public long RequestTime => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Remainder unchanged
Demo fiddle #1 here.
Secondly, if you cannot modify your [JsonPropertyOrder]
attributes (because e.g. you require a different order in a different context), then as long as you are using .NET 6 or later, you could serialize your model to an intermediate JsonNode
hierarchy and sort its properties before finally formatting as a JSON string. To do this, first introduce the following extension methods:
public static partial class JsonExtensions
{
public static JsonNode? SortProperties(this JsonNode? node, bool recursive = true) => node.SortProperties(StringComparer.Ordinal, recursive);
public static JsonNode? SortProperties(this JsonNode? node, IComparer<string> comparer, bool recursive = true)
{
if (node is JsonObject obj)
{
var properties = obj.ToList();
obj.Clear();
foreach (var pair in properties.OrderBy(p => p.Key, comparer))
obj.Add(new (pair.Key, recursive ? pair.Value.SortProperties(comparer, recursive) : pair.Value));
}
else if (node is JsonArray array)
{
foreach (var n in array)
n.SortProperties(comparer, recursive);
}
return node;
}
}
And then you will be able to do:
public string GenerateRequest() => JsonSerializer.SerializeToNode(new MexcSubPersonalPayload(_apiKey, _apiSecret))
.SortProperties(StringComparer.Ordinal)!
.ToJsonString();
Demo fiddle #2 here.
Thirdly, you could create a custom JsonConverter<MexcSubPersonalPayload>
that serializes the properties in the correct order, e.g. by mapping MexcSubPersonalPayload
to a DTO, then serializing the DTO. First define:
class MexcSubPersonalPayloadConverter : JsonConverter<MexcSubPersonalPayload>
{
record MexcSubPersonalPayloadDTO(
[property:JsonPropertyName("api_key"), JsonPropertyOrder(1)] string ApiKey,
[property:JsonPropertyName("op"), JsonPropertyOrder(2)] string Operation,
[property:JsonPropertyName("req_time"), JsonPropertyOrder(3)]long RequestTime,
[property:JsonPropertyName("sign"), JsonPropertyOrder(4)]string Signature);
public override void Write(Utf8JsonWriter writer, MexcSubPersonalPayload value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, new MexcSubPersonalPayloadDTO(value.ApiKey, value.Operation, value.RequestTime, value.Signature), options);
public override MexcSubPersonalPayload Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
}
And then do:
public string GenerateRequest() => JsonSerializer.Serialize(new MexcSubPersonalPayload(_apiKey, _apiSecret), new JsonSerializerOptions { Converters = { new MexcSubPersonalPayloadConverter() }});
Demo fiddle #3 here.
[1] With Json.NET one could override property order via a custom contract resolver, but as of .NET 6 System.Text.Json lacks this flexibility as its contract information is private.
Related Topics
Bind to Selecteditems from Datagrid or Listbox in Mvvm
@Html.Dropdownlistfor How to Set Default Value
Newtonsoft' Could Not Be Found
Unique Random String Generation
Wpf: How to Dynamically Add Controls in Dynamically Created Wpf Window
Console Application Closes Immediately After Opening in Visual Studio
What's the Difference Between Bitmap.Clone() and New Bitmap(Bitmap)
How Big Is an Object Reference in .Net
How to Convert List of Arrays into a Multidimensional Array
How to Check If a String Exists in Another String
Wpf: Binding a Contextmenu to an Mvvm Command
How to Drag and Move Shapes in C#
How to Get the Index of an Element in an Ienumerable
How to Create a Hashcode in .Net (C#) for a String That Is Safe to Store in a Database
How to Get All Instances of All Loaded Types That Implement a Given Interface