Can I serialize nested properties to my class in one operation with Json.net?

前端 未结 3 742
再見小時候
再見小時候 2020-11-27 22:40

Lets say I have a model like:

public class MyModel
{
    public string Name { get; set; }
    public string[] Size { get; set; }
    public string Weight { g         


        
3条回答
  •  青春惊慌失措
    2020-11-27 22:58

    You can do it with the following converter:

    public class MyModelConverter : JsonConverter
    {
        [ThreadStatic]
        static bool cannotWrite;
    
        // Disables the converter in a thread-safe manner.
        bool CannotWrite { get { return cannotWrite; } set { cannotWrite = value; } }
    
        public override bool CanWrite { get { return !CannotWrite; } }
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(MyModel).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var obj = JObject.Load(reader);
            obj.SelectToken("details.size").MoveTo(obj);
            obj.SelectToken("details.weight").MoveTo(obj);
            using (reader = obj.CreateReader())
            {
                // Using "populate" avoids infinite recursion.
                existingValue = (existingValue ?? new MyModel());
                serializer.Populate(reader, existingValue);
            }
            return existingValue;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // Disabling writing prevents infinite recursion.
            using (new PushValue(true, () => CannotWrite, val => CannotWrite = val))
            {
                var obj = JObject.FromObject(value, serializer);
                var details = new JObject();
                obj.Add("details", details);
    
                obj["size"].MoveTo(details);
                obj["weight"].MoveTo(details);
                obj.WriteTo(writer);
            }
        }
    }
    
    public static class JsonExtensions
    {
        public static void MoveTo(this JToken token, JObject newParent)
        {
            if (newParent == null)
                throw new ArgumentNullException();
            if (token != null)
            {
                if (token is JProperty)
                {
                    token.Remove();
                    newParent.Add(token);
                }
                else if (token.Parent is JProperty)
                {
                    token.Parent.Remove();
                    newParent.Add(token.Parent);
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }
        }
    }
    
    public struct PushValue : IDisposable
    {
        Action setValue;
        T oldValue;
    
        public PushValue(T value, Func getValue, Action setValue)
        {
            if (getValue == null || setValue == null)
                throw new ArgumentNullException();
            this.setValue = setValue;
            this.oldValue = getValue();
            setValue(value);
        }
    
        #region IDisposable Members
    
        // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
        public void Dispose()
        {
            if (setValue != null)
                setValue(oldValue);
        }
    
        #endregion
    }
    

    And then use it like this:

    [JsonConverter(typeof(MyModelConverter))]
    public class MyModel
    {
        [JsonProperty("name")]
        public string Name { get; set; }
        [JsonProperty("size")]
        public string[] Size { get; set; }
        [JsonProperty("weight")]
        public string Weight { get; set; }
    }
    
    public class TestClass
    {
        public static void Test()
        {
            string json = @"{
                ""name"" : ""widget"",
                ""details"" : {
                    ""size"" : [
                        ""XL"",""M"",""S"",
                    ],
                    ""weight"" : ""heavy""
                }
            }";
            var mod = JsonConvert.DeserializeObject(json);
            Debug.WriteLine(JsonConvert.SerializeObject(mod, Formatting.Indented));
        }
    }
    

    The ReadJson() method is straightforward: deserialize to a JObject, restructure the appropriate properties, then populate the MyModel class. WriteJson is a little more awkward; the converter needs to temporarily disable itself in a thread-safe manner to generate a "default" JObject that can be then restructured.

提交回复
热议问题