json中skipped value和null value的区别

nkoocmlb  于 2023-05-19  发布在  其他
关注(0)|答案(2)|浏览(305)

我想知道json中的某个属性是否被跳过或提供为null。为此,我使用了这样的setter标志。这工作正常,但它的丑陋得多,我将不得不创建每一个属性,我想检查标志。有没有一个更简洁的解决方案,创建一个自定义类,具有像isSet,Value这样的函数?

public class Tmp2
    {
        private int a;
        public bool isASet;

        private int? b;
        public bool isBSet;

        public int A { get { return a; } 
            set { a = value; isASet = true; } }
        public int? B { get { return b; } 
            set { b = value; isBSet = true; } }
    }

寻找更好的解决方案

eiee3dmh

eiee3dmh1#

您可以采用问题 Custom JSON serializer for optional property with System.Text.Json by Maxime Rossini中的Optional<T>模式,将值 Package 在Optional<T>结构中,该结构跟踪值是否被初始化。由于您使用的是Json.NET,因此需要从System.Text.Json中移植其逻辑。
首先,定义以下接口、结构体和转换器:

public interface IHasValue
{
    bool HasValue { get; }
    object? GetValue();
}

[JsonConverter(typeof(OptionalConverter))]
public readonly struct Optional<T> : IHasValue
{
    //Taken from https://stackoverflow.com/q/63418549/3744182
    //By https://stackoverflow.com/users/547733/maxime-rossini
    public Optional(T value) => (this.HasValue, this.Value) = (true, value);
    public bool HasValue { get; }
    public T Value { get; }
    object? IHasValue.GetValue() => Value;
    public override string ToString() => this.HasValue ? (this.Value?.ToString() ?? "null") : "unspecified";
    public static implicit operator Optional<T>(T value) => new Optional<T>(value);
    public static implicit operator T(Optional<T> value) => value.Value;
}

class OptionalConverter : JsonConverter
{
    static Type? GetValueType(Type objectType) =>
        objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Optional<>) ? objectType.GetGenericArguments()[0] : null;
        
    public override bool CanConvert(Type objectType) => GetValueType(objectType) != null;

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
    {
        var valueType = GetValueType(objectType) ?? throw new ArgumentException(objectType.ToString());
        var value = serializer.Deserialize(reader, valueType);
        return Activator.CreateInstance(objectType, value);
    }

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
        => serializer.Serialize(writer, ((IHasValue?)value)?.GetValue());
}

现在修改你的类(这里是Tmp2),用Optional<T> Package 器替换你想要跟踪的每个属性的值,如下所示:

public class Tmp2
{
    [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] 
    public Optional<int> A { get; set; }
    [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
    public Optional<int?> B { get; set; }
}

现在,您将能够通过检查value.HasValue来判断是否设置了任何特定值,例如:

Assert.That(!JsonConvert.DeserializeObject<Tmp2>("{}")!.A.HasValue);
Assert.That(JsonConvert.SerializeObject(new Tmp2 { B = 32 }) == "{\"B\":32}");

为了在序列化时抑制未设置属性的输出,您有两个选项:
1.用[[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_DefaultValueHandling.htm)标记每个属性: 序列化对象时忽略成员值与成员默认值相同的成员,以便不将其写入JSON。 1.引入一个自定义协定解析程序,它自动禁止未设置属性值的序列化。 上面的Tmp2`版本使用方法#1,但如果您更喜欢#2,请定义以下合约解析器:

public class OptionalResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (property.ValueProvider != null && property.Readable && (typeof(IHasValue).IsAssignableFrom(property.PropertyType) || typeof(IHasValue).IsAssignableTo(property.PropertyType)))
        {
            var old = property.ShouldSerialize;
            Predicate<object> shouldSerialize = (o) => property.ValueProvider.GetValue(o) is IHasValue v ? v.HasValue : true;
            property.ShouldSerialize = (old == null ? shouldSerialize : (o) => old(o) && shouldSerialize(o));
        }
        return property;
    }
}

并按如下方式简化模型:

public record Tmp2(Optional<int> A, Optional<int?> B);

您现在可以使用以下设置往返Tmp2并跟踪属性的存在:

// Cache this statically somewhere for best performance
IContractResolver resolver = new OptionalResolver
{
    // Configure as required, e.g.:
    NamingStrategy = new CamelCaseNamingStrategy(),
};
var settings = new JsonSerializerSettings { ContractResolver = resolver };

var tmp = JsonConvert.DeserializeObject<Tmp2>(json, settings);
var newJson = JsonConvert.SerializeObject(tmp, settings);
Assert.That(JToken.DeepEquals(JToken.Parse(json), JToken.Parse(newJson)));

演示小提琴here

laawzig2

laawzig22#

如果不想向类中添加任何额外代码,可以创建一个通用 Package 器类。此类可用于任何数据模型。

public class Tmp2
{
    public int A { get; set; }

    public int? B { get; set; }
}

public class Wraper<T>
{
    public List<string> IsValChanged { get; set; }
    public T Tmp { get; set; }
    
    private void Init(JObject tmp)
    {
        IsValChanged = tmp.Properties().DescendantsAndSelf().OfType<JProperty>()
                          .Select(jp => jp.Name).ToList();
        Tmp = tmp.ToObject<T>();
    }
    
    public Wraper(JObject tmp)
    {
        Init(tmp);
    }
    public Wraper(string json)
    {
        Init(JObject.Parse(json));
    }

    public bool IsPropertyChanged(object prop, [CallerArgumentExpression("prop")] string propName = null)
    {
        return IsValChanged.Any(x => x == propName.Substring(propName.IndexOf(".") + 1));
    }
}

测试

var json1 = "{\"A\":0}";
    var json2 = "{\"B\":null}";
    var json3 = "{\"A\":0,\"B\":null}";

    var wrapper = new Wraper<Tmp2>(json2);

    Tmp2 tmp = wrapper.Tmp;
    var valueChangedList = string.Join(", ", wrapper.IsValChanged);

    Console.WriteLine($"Changed properties: {valueChangedList}");
    Console.WriteLine($"{nameof(tmp.A)} changed: {wrapper.IsPropertyChanged(tmp.A)}");
    Console.WriteLine($"{nameof(tmp.B)} changed: {wrapper.IsPropertyChanged(tmp.B)}");

试验结果

Changed properties: B
A changed: False
B changed: True

以及如何在控制器动作中使用 Package 器

public ActionResult Post([FromBody] JObject model)
{
    var wrapper = new Wraper<Tmp2>(model);
    Tmp2 tmp = wrapper.Tmp;
    var valueChangedList = string.Join(", ", wrapper.IsValChanged);
    // another code
}

相关问题