System.Text.JSON API Is There Something Like Icontractresolver

System.Text.Json API is there something like IContractResolver


The equivalent types in System.Text.Json -- JsonClassInfo and JsonPropertyInfo -- 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



Leave a reply



Submit