When a field is of object type, System.Text.Json automatically sets it to the JsonElement type instead of the corresponding type, leading to strange issues in many cases.
Therefore, there are two places where code needs to be added to alleviate this situation.
The first step is to implement a converter that, when a type is an object, directly uses the actual type instead of JsonElement if the JSON is a simple type.
However, this approach is ineffective when the field is a dictionary or an array, such as object[]
, T[]
where T contains object fields, etc.
public class ObjectJsonSerializer : IJsonSerializer
{
private readonly JsonSerializerOptions _options;
public ObjectJsonSerializer()
{
_options = new JsonSerializerOptions();
_options.Converters.Add(new ObjectToInferredTypesConverter());
}
/// <inheritdoc />
public TObject? Deserialize<TObject>(ReadOnlySpan<byte> bytes)
where TObject : class
{
return System.Text.Json.JsonSerializer.Deserialize<TObject>(bytes, _options);
}
/// <inheritdoc />
public byte[] Serialize<TObject>(TObject obj)
where TObject : class
{
return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(obj, _options);
}
}
public class ObjectToInferredTypesConverter : JsonConverter<object>
{
public override object Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) => reader.TokenType switch
{
JsonTokenType.True => true,
JsonTokenType.False => false,
JsonTokenType.Number when reader.TryGetInt64(out long l) => l,
JsonTokenType.Number => reader.GetDouble(),
JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime,
JsonTokenType.String => reader.GetString()!,
_ => JsonDocument.ParseValue(ref reader).RootElement.Clone()
};
public override void Write(
Utf8JsonWriter writer,
object objectToWrite,
JsonSerializerOptions options) =>
System.Text.Json.JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options);
}
To accommodate more cases, manual conversion is necessary, as shown in the extension methods below:
/// <summary>
/// Extension methods.
/// </summary>
public static class JsonConvertExtensions
{
/// <summary>
/// Convert to corresponding type.
/// </summary>
/// <param name="json"></param>
/// <returns>object.</returns>
public static object? ToObject(this JsonElement json)
{
switch (json.ValueKind)
{
case JsonValueKind.True:
return json.GetBoolean();
case JsonValueKind.False:
return json.GetBoolean();
case JsonValueKind.String:
return json.GetString();
case JsonValueKind.Null:
return null;
case JsonValueKind.Object:
return json;
case JsonValueKind.Number:
return json.GetInt32();
}
return json;
}
/// <summary>
/// Convert to corresponding type.
/// </summary>
/// <param name="obj"></param>
/// <returns>object.</returns>
public static object? ToObject(this object? obj)
{
if (obj == null)
{
return obj;
}
if (obj is JsonElement json)
{
switch (json.ValueKind)
{
case JsonValueKind.True:
return json.GetBoolean();
case JsonValueKind.False:
return json.GetBoolean();
case JsonValueKind.String:
return json.GetString();
case JsonValueKind.Null:
return null;
case JsonValueKind.Object:
return json;
case JsonValueKind.Number:
return json.GetInt32();
}
return json;
}
return obj;
}
}
For arrays and other types, manual usage of the extension method is necessary:
for (int i = 0; i < message.Body.Data.Length; i++)
{
message.Body.Data[i].Value = message.Body.Data[i].Value.ToObject();
}
文章评论