使用System.Text.Json将“false”反序列化为“null

lymgl2op  于 2023-02-26  发布在  其他
关注(0)|答案(3)|浏览(195)

我正在访问一个api,当null被正常使用时,由于某种原因,它返回false
我现在需要一种方法来将任意json字符串反序列化为完全泛型类型,并将任何可空/引用类型属性的JsonTokenType.False解释为JsonTokenType.Null,无论其嵌套有多深。
(我的目标是net7.0netstandard2.0,并使用System.Text.Json

示例数据(需要适用于任何模式!)

{
   "property": false // expected type: string
   "property2": false // expected type: int
   "property3": { // data might be nested
      "property": false // expected type SomeType
   }
}
public sealed class JsonDefinition {
   public string? Property { get; set; }
   public int? Property2 { get; set; }
   public NestedJsonType? Property { get; set; }
}
public sealed class NestedJsonType{
   public SomeType? Property { get; set; }
}
5jvtdoz2

5jvtdoz21#

    • 如果您使用的是. NET 7**(或System.Text.Json nuget version 7)或更高版本,则可以添加DefaultJsonTypeInfoResolver修饰符,该修饰符将"false as default"转换器应用于反序列化过程中遇到的每种类型的每个属性:

首先,创建以下Action<JsonTypeInfo>修改器:

public static partial class JsonExtensions
{
    public static Action<JsonTypeInfo> AddFalseToDefaultPropertyConverter { get; } = 
        static typeInfo => 
        {
            if (typeInfo.Kind != JsonTypeInfoKind.Object)
                return;
            foreach (var property in typeInfo.Properties)
            {
                // TODO: Modify these checks as required.
                // For instance if PropertyType is declared as typeof(object) or typeof(JsonNode) or typeof(JsonElement?) you may want to let `false` be deserialized as-is.
                if ((!property.PropertyType.IsValueType || Nullable.GetUnderlyingType(property.PropertyType) != null) && property.CustomConverter == null)
                    property.CustomConverter = (JsonConverter)Activator.CreateInstance(typeof(FalseAsDefaultConverter<>).MakeGenericType(property.PropertyType))!;
            }
        };
    
    class FalseAsDefaultConverter<T> : JsonConverter<T>
    {
        public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
            reader.TokenType switch
            {
                JsonTokenType.False => default,
                _ =>JsonSerializer.Deserialize<T>(ref reader, options),
            };
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => 
            JsonSerializer.Serialize(writer, value, options);
    }
}

现在,您将能够反序列化JSON,如下所示:

var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers = { JsonExtensions.AddFalseToDefaultPropertyConverter },
    },
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    ReadCommentHandling = JsonCommentHandling.Skip,
    // Add other options as required:
    WriteIndented = true,
};

var model = JsonSerializer.Deserialize<JsonDefinition>(input, options);

注:

  • 因为转换器直接应用于每个序列化属性,所以递归没有问题。
  • 此解决方案不处理根对象集合项false值。
  • 您可能需要调整选择要应用转换器的属性类型的// TODO: Modify these checks as required.逻辑。

演示小提琴#1 here

    • 如果您使用的是. NET 6**,则合约定制不可用。相反,您可以预加载到JsonNode层次结构中,然后递归删除所有false属性值:
public static partial class JsonExtensions
{
    public static JsonNode? RemoveFalseProperties(this JsonNode? root)
    {
        foreach (var item in root.DescendantItemsAndSelf(false).Where(i => i.name != null && i.node is JsonValue v && v.TryGetValue<bool>(out var b) && b == false).ToList())
            ((JsonObject)item.parent!).Remove(item.name!);
        return root;
    }
    
    //Taken from https://stackoverflow.com/questions/73887517/how-to-recursively-descend-a-system-text-json-jsonnode-hierarchy-equivalent-to
    /// Recursively enumerates all JsonNodes (including their index or name and parent) in the given JsonNode object in document order.
    public static IEnumerable<(JsonNode? node, int? index, string? name, JsonNode? parent)> DescendantItemsAndSelf(this JsonNode? root, bool includeSelf = true) => 
        RecursiveEnumerableExtensions.Traverse(
            (node: root, index: (int?)null, name: (string?)null, parent: (JsonNode?)null),
            (i) => i.node switch
            {
                JsonObject o => o.AsDictionary().Select(p => (p.Value, (int?)null, p.Key.AsNullableReference(), i.node.AsNullableReference())),
                JsonArray a => a.Select((item, index) => (item, index.AsNullableValue(), (string?)null, i.node.AsNullableReference())),
                _ => i.ToEmptyEnumerable(),
            }, includeSelf);

    static IEnumerable<T> ToEmptyEnumerable<T>(this T item) => Enumerable.Empty<T>();
    static T? AsNullableReference<T>(this T item) where T : class => item;
    static Nullable<T> AsNullableValue<T>(this T item) where T : struct => item;
    static IDictionary<string, JsonNode?> AsDictionary(this JsonObject o) => o;
}

public static partial class RecursiveEnumerableExtensions
{
    // Rewritten from the answer by Eric Lippert https://stackoverflow.com/users/88656/eric-lippert
    // to "Efficient graph traversal with LINQ - eliminating recursion" http://stackoverflow.com/questions/10253161/efficient-graph-traversal-with-linq-eliminating-recursion
    // to ensure items are returned in the order they are encountered.
    public static IEnumerable<T> Traverse<T>(
        T root,
        Func<T, IEnumerable<T>> children, bool includeSelf = true)
    {
        if (includeSelf)
            yield return root;
        var stack = new Stack<IEnumerator<T>>();
        try
        {
            stack.Push(children(root).GetEnumerator());
            while (stack.Count != 0)
            {
                var enumerator = stack.Peek();
                if (!enumerator.MoveNext())
                {
                    stack.Pop();
                    enumerator.Dispose();
                }
                else
                {
                    yield return enumerator.Current;
                    stack.Push(children(enumerator.Current).GetEnumerator());
                }
            }
        }
        finally
        {
            foreach (var enumerator in stack)
                enumerator.Dispose();
        }
    }
}

然后,要进行反序列化,请执行以下操作:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    ReadCommentHandling = JsonCommentHandling.Skip,
    // Add other options as required:
    WriteIndented = true,
};

var model = JsonNode.Parse(input, documentOptions : new() { CommentHandling = JsonCommentHandling.Skip })
    .RemoveFalseProperties()
    .Deserialize<JsonDefinition>(options);

注:

  • 这种解决方案比input.Replace("false", "null")之类的简单字符串替换更健壮,因为文本false很可能出现在某个JSON字符串中。
  • System.Text.json.Nodes缺少一个JContainer.Descendants()的等价物,所以我们必须自己编写一个。

演示#2 here
我删除了false属性,因为它看起来比用null替换它们更健壮。如果你想用null替换,你需要添加一个JsonConverter<bool>,它将nullMap到false以获得bool值。

public class BoolConverter : JsonConverter<bool>
{
    public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) =>
        writer.WriteBooleanValue(value);
    
    public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        reader.TokenType switch
        {
            JsonTokenType.True => true,
            JsonTokenType.False => false,
            JsonTokenType.Null => false,
            _ => throw new JsonException(),
        };
}

public static partial class JsonExtensions
{
    public static JsonNode? ReplaceFalseWithNullProperties(this JsonNode? root)
    {
        foreach (var item in root.DescendantItemsAndSelf(false).Where(i => i.name != null && i.node is JsonValue v && v.TryGetValue<bool>(out var b) && b == false).ToList())
            ((JsonObject)item.parent!)[item.name!] = null;
        return root;
    }
}

然后反序列化如下:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    ReadCommentHandling = JsonCommentHandling.Skip,
    Converters = { new BoolConverter() },
    // Add other options as required:
    WriteIndented = true,
};

var model = JsonNode.Parse(input, documentOptions : new() { CommentHandling = JsonCommentHandling.Skip })
    .ReplaceFalseWithNullProperties()
    .Deserialize<JsonDefinition>(options);

演示#3 here

6l7fqoea

6l7fqoea2#

我想我现在就用input.Replace("false", "null")之类的东西吧...

brvekthn

brvekthn3#

System.Text.Json只适合于序列化“Hello World”的演示。我尝试过使用它,但是代码变得不必要的复杂。所以我给予你一个Newtonsoft.Json转换器。如果你是Text.Json的粉丝,尝试转换到它,你就会明白我在说什么

JsonDefinition data = JsonConvert.DeserializeObject<JsonDefinition>(json, new FalseJsonConverter<JsonDefinition>());

public class FalseJsonConverter<T> : JsonConverter<T> 
{
    public override void WriteJson(JsonWriter writer, T? value, Newtonsoft.Json.JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        JObject o = JObject.Load(reader);

        var falses = o.DescendantsAndSelf()
                    .Where(x => (x.Type == JTokenType.Boolean))
                    .Select(x => (JProperty)x.Parent)
                    .ToList();

        for (var i = 0; i < falses.Count(); i++)
            if ((Boolean)falses[i].Value == false) falses[i].Value = null;

        return o.ToObject<T>();
    }

    public override bool CanRead
    {
        get { return true; }
    }
}

相关问题