Is there a way in Json.NET serialization to distinguish between “null because not present” and “null because null”?

后端 未结 5 899
日久生厌
日久生厌 2020-12-01 10:51

I\'m working in an ASP.NET webapi codebase where we rely heavily on the automatic support for JSON deserialization of message bodies into .NET objects via JSON.NET.

5条回答
  •  谎友^
    谎友^ (楼主)
    2020-12-01 11:30

    The most elegant solution I came up with came to me in an epiphany:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    
    namespace testJsonDeserializer
    {
        class Program
        {
            static void Main(string[] args)
            {
                // this operator has the password set to meow. 
                Operator originalOperator = new Operator
                {
                    OperatorGuid = Guid.Parse("3bb1dc84-2963-4921-a567-fb2e7475623d"),
                    UserName = "noortje@peterhuppertz.net",
                    Password = "meow",
                    PropertyThatWillBeNulled = "noortje@peterhuppertz.net",
                };
    
                // this json EXPLICITLY sets the PropertyThatWillBeNulled to null, but omits the Password property, making it null IMPLICITLY. 
                string json =
                    "{ \"OperatorGuid\":\"3bb1dc84-2963-4921-a567-fb2e7475623d\", \"UserName\": \"noortje@peterhuppertz.net\", \"Email\": null }";
                // What a PATCH would want for the target object is to leave implicit Nulls unchanged, but explicit nulls set to null. 
    
                Operator patchedOperator = JsonConvert.DeserializeObject(json);
                // At this stage, our patched operator has the password set to null. We do not want that; we want to keep whatever is stored in originalOperator
    
                Operator opToStore = MapJsonToOperator(patchedOperator, originalOperator, json);
    
                Console.WriteLine("Our patched operator:");
                Console.WriteLine($"Guid: {opToStore.OperatorGuid}");
                Console.WriteLine($"UserName: {opToStore.UserName}");
                Console.WriteLine($"Password: {opToStore.Password}");
                Console.WriteLine($"Email: {opToStore.PropertyThatWillBeNulled}");
                Console.ReadKey();
            }
    
            private static Operator MapJsonToOperator(Operator source, Operator original, string json)
            {
                Operator result = new Operator
                {
                    OperatorGuid = source.OperatorGuid,
                    UserName = source.UserName != null
                        // we check if the source property has a value, if so, we use that value.
                        ? source.UserName
                        // if it doesn't, we check the Json to see if the value is in there, explicitly set to NULL. If it is, we set it to NULL as well
                        : (IsNullValueExplicit(json, "UserName") ? null 
                            // if it is not in the json (making it implicitly null), we just leave the value as it was. 
                            : original.UserName),
                    PropertyThatWillBeNulled = source.PropertyThatWillBeNulled != null
                        ? source.PropertyThatWillBeNulled
                        : (IsNullValueExplicit(json, "Email") ? null : original.PropertyThatWillBeNulled),
                    Password = source.Password != null
                        ? source.Password
                        : (IsNullValueExplicit(json, "Password") ? null : original.Password),
                };
    
                return result;
            }
    
            static bool IsNullValueExplicit(string json, string fieldName)
            {
                JToken outer = JToken.Parse(json);
                JObject inner = outer.Value();
                List keys = inner.Properties().Select(p => p.Name).ToList();
                return keys.Contains(fieldName);
            }
        }
    
        public class Operator
        {
            public Guid OperatorGuid { get; set; }
            public string UserName { get; set; }
            public string Password { get; set; }
            public string PropertyThatWillBeNulled { get; set; }
        }
    }
    

    I know, there are a lot of comments in here. Maybe I overexplained... but I thought I'd err on the side of caution.

提交回复
热议问题