如何处理通过 JSON 传递的映射故意 NULL 值?

How to deal with mapping intentional NULL values passed through JSON?

总结

假设我正在设计一个 API 来执行 SQL 服务器 SELECT 查询。我有几个必需参数和一些可选参数。但是,如果在有效负载中发送 null 值,这是 correctintentional,但我我无法区分当前反序列化 JSON 的方式。我要反序列化的 属性 的值默认为空。我的问题是我无法判断它是否已填写,因为没有标记。

示例

以我目前的方式(如下例),我无法区分用户是否想要:

  1. 实际寻找空值
  2. 如果用户对查找特定字段不感兴趣,则从序列化中删除空值
using Newtonsoft.Json;

namespace test
{
    public class SomeClass
    {
        public string RequiredProperty {get;set;}
        public string RequiredProperty2 {get;set;}
        public string OptionalProperty3 {get;set;}

        public SomeClass(){}

    }

    class Program
    {
        static void Main(string[] args)
        {   
            // Example JSON payloads
            string JsonExample1 = @"
            {
                ""RequiredProperty"":""search"",
                ""RequiredProperty2"":""this"",
                ""OptionalProperty3"":null
            }";

            string JsonExample2 = @"
            {
                ""RequiredProperty"":""search"",
                ""RequiredProperty2"":""this""
            }";
            
            // Deserializing JsonExample1
            SomeClass sc1 = JsonConvert.DeserializeObject<SomeClass>(JsonExample1);

            // Deserializing JsonExample2 - identical to Example1 even though the INTENTION is completely different
            SomeClass sc2 = JsonConvert.DeserializeObject<SomeClass>(JsonExample2);

            // Now, using the model, I am unable to tell what the user's intentions actually were.
        }
    }
}   

问题

1、我对这个问题甚至的处理方法是否正确? 2. 甚至 适当 让可选参数通过 JSON 传递?我选择对所有内容使用 JSON 有效载荷,因为我工作的公司有这些需要传递的疯狂有效载荷。

Andy提供的评论是正确的,我也不知道你在问题中提到的两者之间的区别。我认为 OptionalProperty3 的值为 null 与 OptionalProperty3 is not-set.

相同

如果你还想区分它们,这里我可以提供一个解决方法供你参考。将 JsonExample1 中的 null 替换为 "null" 字符串,请参考我下面的代码:

我想到的解决方案。我在使用 JsonConverter 时遇到了很多问题,所以我选择使用 ContractResolver。似乎这只是我们目前如何执行 API 的一般限制。关于这个其实有很多讨论。确实没有办法区分默认值和已设置的内容。我只是选择创建一些字符串填充值来指示用户已发送 JSON 中的值。这使 JSON 与传递时发送的完全相同。但是,映射的内容最终完全不同,开发人员可以看出现在实际上还没有设置某些内容。

不幸的是,我不得不想出 2 个契约解析器,因为它们本身并不处理对象的 实例 ,而且我对当前对象的条件序列化很感兴趣实例。我最终不得不实例化我的自定义合同解析器并传递我感兴趣的对象的实例。

如果有人知道如何更好地做到这一点,请告诉我!总体而言,我花了很多时间浏览 GitHub 并将不同的堆栈溢出帖子拼凑在一起。

代码: 解串器

// PreInstalled Packages
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

// From NuGet - Default
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

    public class CrudDeserializer: DefaultContractResolver
    {
        // Designated respresentation for a null value passed through JSON, its default is "JsonNull"
        private string NullRepresentation;

        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            return type.GetProperties()
                    .Select(p=>{
                        var jp = base.CreateProperty(p, memberSerialization);
                        jp.ValueProvider = new NullToUniqueStringValueProvider(p, this.NullRepresentation);
                        return jp;
                    }).ToList();
        }

        public CrudDeserializer(string nullRepresentation = "JsonNull")
        {
            if(nullRepresentation == null)
            {
                throw new Exception ("nullRepresentation cannot be NULL. It kind of defeats the purpose");
            }

            this.NullRepresentation = nullRepresentation;
        }
    }


// Second class
    public class NullToUniqueStringValueProvider : IValueProvider
    {
        private PropertyInfo MemberInfo;
        private string NullRepresentation;

        public NullToUniqueStringValueProvider(PropertyInfo memberInfo, string nullRepresentation)
        {
            this.MemberInfo = memberInfo;
            this.NullRepresentation = nullRepresentation;
        }

        public object GetValue(object target)
        {
            throw new Exception("This class is not used for serialization");
        }

        public void SetValue(object target, object value)
        {
            if ((string)value == null)
            {
                MemberInfo.SetValue(target, this.NullRepresentation);
            }
            else
            {
                MemberInfo.SetValue(target, value);
            }
        }
    }

序列化器

// PreInstalled Packages
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

// From NuGet - Default
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

    public class CrudSerializer<T>: DefaultContractResolver
    {
        private string NullRepresentation;
        private T InstantiatedObject;


        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            return type.GetProperties()
                    .Select(p=>{
                        var jp = this.CreateProperty(p, memberSerialization);
                        jp.ValueProvider = new UniqueStringToNull(p, this.NullRepresentation);
                        return jp;
                    }).ToList();
        }

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty property = base.CreateProperty(member, memberSerialization);
            PropertyInfo pi = member as PropertyInfo;
            string value =  (string)pi.GetValue(this.InstantiatedObject);
            if (value == null)
            {
                property.ShouldSerialize =
                    instance =>
                    {
                        return false;
                    };
            }

            return property;
    }

        public CrudSerializer(T someInstance, string nullRepresentation = "JsonNull")
        {
            if(nullRepresentation == null)
            {
                throw new Exception ("nullRepresentation cannot be NULL. It kind of defeats the purpose");
            }

            this.NullRepresentation = nullRepresentation;
            this.InstantiatedObject = someInstance;
        }
    }

    public class UniqueStringToNull : IValueProvider
    {
        private PropertyInfo MemberInfo;
        private string NullRepresentation;

        public UniqueStringToNull(PropertyInfo memberInfo, string nullRepresentation)
        {
            this.MemberInfo = memberInfo;
            this.NullRepresentation = nullRepresentation;
        }

        public object GetValue(object target)
        {
            object result =  MemberInfo.GetValue(target);
            if (MemberInfo.PropertyType == typeof(string) && (string)result == this.NullRepresentation)
            {
                result = null;
            }
            return result;

        }

        public void SetValue(object target, object value)
        {
            throw new Exception ("This ContractResolver cannot be used for deserialization");
        }
    }

示例:

    public class SomeClass
    {
        
        public string RequiredProperty {get;set;}
        public string RequiredProperty2 {get;set;}

        public string OptionalProperty3 {get;set;}
        public string OptionalProperty4 {get;set;}

        public SomeClass(){}

    }
    class Program
    {
        static void Main(string[] args)
        {   
            // Example JSON payloads
            string JsonExample1 = @"
            {""RequiredProperty"":""search"",""RequiredProperty2"":""this"",""OptionalProperty3"": null}";

            string JsonExample2 = @"
            {""RequiredProperty"":""search"",""RequiredProperty2"":""this""}";
            
            JsonSerializerSettings deserializationSettings = new JsonSerializerSettings { 
                ContractResolver = new CrudDeserializer()
                };
                

            // Deserializing/Serializing JsonExample1
            SomeClass sc1 = JsonConvert.DeserializeObject<SomeClass>(JsonExample1, deserializationSettings );

            // Passing over current instance of object to ContractResolver
            JsonSerializerSettings serializationSettings1 = new JsonSerializerSettings { 
                ContractResolver = new CrudSerializer<SomeClass>(sc1)
                };
            string json1 =  JsonConvert.SerializeObject(sc1, Formatting.None, serializationSettings1);


            // Deserializing/Serializing JsonExample2 
            SomeClass sc2 = JsonConvert.DeserializeObject<SomeClass>(JsonExample2, deserializationSettings);

            // Passing over current instance of object to ContractResolver
            JsonSerializerSettings serializationSettings2 = new JsonSerializerSettings { 
                ContractResolver = new CrudSerializer<SomeClass>(sc2)
                };

            string json2 =  JsonConvert.SerializeObject(sc2,  Formatting.None, serializationSettings2);

            // Done, JSON is the exact same no matter how many times I deserialize.
            // However, in the background, I am able to tell now if a NULL value was sent!
        }

    }