【问题标题】:Json.net how to serialize object as valueJson.net 如何将对象序列化为值
【发布时间】:2014-06-29 01:26:06
【问题描述】:

我仔细阅读了文档、StackOverflow 等,似乎找不到这个...

我想要做的是将一个简单的值类型的对象序列化/反序列化为一个值,而不是一个对象,如下所示:

public class IPAddress
{
    byte[] bytes;

    public override string ToString() {... etc.
}

public class SomeOuterObject
{
    string stringValue;
    IPAddress ipValue;
}

IPAddress ip = new IPAddress("192.168.1.2");
var obj = new SomeOuterObject() {stringValue = "Some String", ipValue = ip};
string json = JsonConverter.SerializeObject(obj);

我想要的是让 json 像这样序列化:

// json = {"someString":"Some String","ipValue":"192.168.1.2"} <- value serialized as value, not subobject

不是 ip 成为嵌套对象的地方,例如:

// json = {"someString":"Some String","ipValue":{"value":"192.168.1.2"}}

有人知道怎么做吗?谢谢! (附注:我在一个庞大的遗留 .NET 代码库上使用 Json 序列化,所以我不能真正更改任何现有类型,但我可以扩充/分解/装饰它们以促进 Json 序列化。)

【问题讨论】:

  • 应该像var obj = new () {ipValue = ip.Value};一样简单,其中Value是存储IP地址的属性。
  • 我建议你接受一个答案。

标签: c# json.net


【解决方案1】:

您可以使用 IPAddress 类的自定义 JsonConverter 来处理此问题。这是您需要的代码:

public class IPAddressConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(IPAddress));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return new IPAddress(JToken.Load(reader).ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JToken.FromObject(value.ToString()).WriteTo(writer);
    }
}

然后,将[JsonConverter] 属性添加到您的IPAddress 类,您就可以开始了:

[JsonConverter(typeof(IPAddressConverter))]
public class IPAddress
{
    byte[] bytes;

    public IPAddress(string address)
    {
        bytes = address.Split('.').Select(s => byte.Parse(s)).ToArray();
    }

    public override string ToString() 
    { 
        return string.Join(".", bytes.Select(b => b.ToString()).ToArray()); 
    }
}

这是一个工作演示:

class Program
{
    static void Main(string[] args)
    {
        IPAddress ip = new IPAddress("192.168.1.2");
        var obj = new SomeOuterObject() { stringValue = "Some String", ipValue = ip };
        string json = JsonConvert.SerializeObject(obj);
        Console.WriteLine(json);
    }
}

public class SomeOuterObject
{
    public string stringValue { get; set; }
    public IPAddress ipValue { get; set; }
}

输出:

{"stringValue":"Some String","ipValue":"192.168.1.2"}

【讨论】:

    【解决方案2】:

    这是对Customise NewtonSoft.Json for Value Object serialisation 的回答,关于 DDD 中的值对象。但是这个问题被标记为与这个问题重复,我认为这并不完全正确。

    我借用了 ValueObjectConverter 的代码 https://github.com/eventflow/EventFlow,我只是做了一些小改动。

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using FluentAssertions;
    using Newtonsoft.Json;
    using Xunit;
    
    namespace Serialization
    {
        public class ValueObjectSerializationTests
        {
            class SomeClass
            {
                public IPAddress IPAddress { get; set; }
            }
    
            [Fact]
            public void FactMethodName()
            {
                var given = new SomeClass
                {
                    IPAddress = new IPAddress("192.168.1.2")
                };
    
                var jsonSerializerSettings = new JsonSerializerSettings()
                {
                    Converters = new List<JsonConverter>
                                 {
                                    new ValueObjectConverter()
                                 }
                };
                var json = JsonConvert.SerializeObject(given, jsonSerializerSettings);
    
                var result = JsonConvert.DeserializeObject<SomeClass>(json, jsonSerializerSettings);
    
                var expected = new SomeClass
                {
                    IPAddress = new IPAddress("192.168.1.2")
                };
    
                json.Should().Be("{\"IPAddress\":\"192.168.1.2\"}");
                expected.ShouldBeEquivalentTo(result);
            }
        }
    
        public class IPAddress:IValueObject
        {
            public IPAddress(string value)
            {
                Value = value;
            }
    
            public object GetValue()
            {
                return Value;
            }
    
            public string Value { get; private set; }
        }
    
        public interface IValueObject
        {
            object GetValue();
        }
    
        public class ValueObjectConverter : JsonConverter
        {
            private static readonly ConcurrentDictionary<Type, Type> ConstructorArgumenTypes = new ConcurrentDictionary<Type, Type>();
    
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                if (!(value is IValueObject valueObject))
                {
                    return;
                }
    
                serializer.Serialize(writer, valueObject.GetValue());
            }
    
            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                var parameterType = ConstructorArgumenTypes.GetOrAdd(
                    objectType,
                    t =>
                    {
                        var constructorInfo = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
                        var parameterInfo = constructorInfo.GetParameters().Single();
                        return parameterInfo.ParameterType;
                    });
    
                var value = serializer.Deserialize(reader, parameterType);
                return Activator.CreateInstance(objectType, new[] { value });
            }
    
            public override bool CanConvert(Type objectType)
            {
                return typeof(IValueObject).IsAssignableFrom(objectType);
            }
        }
    }
    

    【讨论】:

    • 对我来说效果很好:-)
    【解决方案3】:
    public class IPAddress
    {
        byte[] bytes;
    
        public override string ToString() {... etc.
    }
    
    IPAddress ip = new IPAddress("192.168.1.2");
    var obj = new () {ipValue = ip.ToString()};
    string json = JsonConverter.SerializeObject(obj);
    

    您正在序列化整个 IP 地址实例。也许只是尝试将地址序列化为字符串。 (这假定您已经实现了 ToString 方法。)

    【讨论】:

    • 谢谢伯克。我应该提到我在一个地方这样做,因为我已经必须解复用一堆不同的对象类型,但是当 IPAddress 是许多对象的强类型属性时,我认为尝试手动处理它们会很痛苦.
    【解决方案4】:

    使用Cinchoo ETL - 一个解析/写入JSON文件的开源库,您可以通过ValueConverter或回调机制控制每个对象成员的序列化。

    方法一:

    下面的示例展示了如何使用成员级 ValueConverters 序列化“SomeOuterObject”

    public class SomeOuterObject
    {
        public string stringValue { get; set; }
        [ChoTypeConverter(typeof(ToTextConverter))]
        public IPAddress ipValue { get; set; }
    }
    

    而值转换器是

    public class ToTextConverter : IChoValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.ToString();
        }
    }
    

    最后将对象序列化为文件

    using (var jr = new ChoJSONWriter<SomeOuterObject>("ipaddr.json")
        )
    {
        var x1 = new SomeOuterObject { stringValue = "X1", ipValue = IPAddress.Parse("12.23.21.23") };
        jr.Write(x1);
    }
    

    输出是

    [
     {
      "stringValue": "X1",
      "ipValue": "12.23.21.23"
     }
    ]
    

    方法二:

    这是将值转换器回调连接到“ipValue”属性的替代方法。这种方法是精益的,不需要为这个操作创建价值转换器。

    using (var jr = new ChoJSONWriter<SomeOuterObject>("ipaddr.json")
        .WithField("stringValue")
        .WithField("ipValue", valueConverter: (o) => o.ToString())
        )
    {
        var x1 = new SomeOuterObject { stringValue = "X1", ipValue = IPAddress.Parse("12.23.21.23") };
        jr.Write(x1);
    }
    

    希望这会有所帮助。

    免责声明:我是图书馆的作者。

    【讨论】:

      【解决方案5】:

      这是一个用于简单值对象的通用转换的类,我计划在 Activout.RestClient 的下一次更新中包含它。一个“简单值对象”作为一个对象,它具有:

      1. 没有默认构造函数
      2. 名为 Value 的公共属性
      3. 与 Value 属性具有相同类型的构造函数

      var settings = new JsonSerializerSettings
      {
          Converters = new List<JsonConverter> {new SimpleValueObjectConverter()}
      };
      

      public class SimpleValueObjectConverter : JsonConverter
      {
          public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
          {
              var valueProperty = GetValueProperty(value.GetType());
              serializer.Serialize(writer, valueProperty.GetValue(value));
          }
      
          public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
              JsonSerializer serializer)
          {
              var valueProperty = GetValueProperty(objectType);
              var value = serializer.Deserialize(reader, valueProperty.PropertyType);
              return Activator.CreateInstance(objectType, value);
          }
      
          public override bool CanConvert(Type objectType)
          {
              if (GetDefaultConstructor(objectType) != null) return false;
              var valueProperty = GetValueProperty(objectType);
              if (valueProperty == null) return false;
              var constructor = GetValueConstructor(objectType, valueProperty);
              return constructor != null;
          }
      
          private static ConstructorInfo GetValueConstructor(Type objectType, PropertyInfo valueProperty)
          {
              return objectType.GetConstructor(new[] {valueProperty.PropertyType});
          }
      
          private static PropertyInfo GetValueProperty(Type objectType)
          {
              return objectType.GetProperty("Value");
          }
      
          private static ConstructorInfo GetDefaultConstructor(Type objectType)
          {
              return objectType.GetConstructor(new Type[0]);
          }
      }
      

      【讨论】:

        【解决方案6】:

        有几种不同的方法可以解决这个问题,具体取决于您能够付出的努力程度以及对现有类更改的容忍度。

        一种方法是将您的类定义为DataContract,并将类中的元素明确标识为DataMembers。 Netwonsoft 在其序列化中识别并使用这些属性。这种方法的好处是类现在可以使用其他使用数据契约序列化的方法进行序列化。

            [DataContract]
            public class IPAddress
            {
                private byte[] bytes;
        
                // Added this readonly property to allow serialization
                [DataMember(Name = "ipValue")]
                public string Value
                {
                    get
                    {
                        return this.ToString();
                    }
                }
        
                public override string ToString()
                {
                    return "192.168.1.2";
                }
            }
        

        这是我用来序列化的代码(我可能使用的是旧版本,因为我没有看到 SerializeObject 方法):

                IPAddress ip = new IPAddress();
        
                using (StringWriter oStringWriter = new StringWriter())
                {
                    using (JsonTextWriter oJsonWriter = new JsonTextWriter(oStringWriter))
                    {
                        JsonSerializer oSerializer = null;
                        JsonSerializerSettings oOptions = new JsonSerializerSettings();
        
                        // Generate the json without quotes around the name objects
                        oJsonWriter.QuoteName = false;
                        // This can be used in order to view the rendered properties "nicely"
                        oJsonWriter.Formatting = Formatting.Indented;
                        oOptions.NullValueHandling = NullValueHandling.Ignore;
        
                        oSerializer = JsonSerializer.Create(oOptions);
        
                        oSerializer.Serialize(oJsonWriter, ip);
        
                        Console.WriteLine(oStringWriter.ToString());
                    }
                }
        

        这是输出:

        {
          ipValue: "192.168.1.2"
        }
        

        另一种方法是创建您自己的 JsonConverter 继承器,它可以准确地序列化您需要的内容,而无需修改类的内部:

        public class JsonToStringConverter : JsonConverter
        {
            public override bool CanConvert(Type objectType)
            {
                return true;
            }
            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                throw new NotImplementedException();
            }
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                writer.WriteStartObject();
                writer.WritePropertyName(value.GetType().Name);
                writer.WriteValue(Convert.ToString(value));
                writer.WriteEndObject();
            }
        }
        

        这个类只写了类的tostring值和类名。更改名称可以通过类的附加属性来完成,我没有展示。

        该类将如下所示:

            [JsonConverter(typeof(JsonToStringConverter))]
            public class IPAddress
            {
                private byte[] bytes;
        
                public override string ToString()
                {
                    return "192.168.1.2";
                }
            }
        

        输出是:

        {
          IPAddress: "192.168.1.2"
        }
        

        【讨论】:

        • 感谢主管技术。我应该提供一个更完整的例子。 “ipValue”不是 IPAddress 对象内的字符串值的名称,这很容易做到。 ipValue 是包含类中的强类型 IPAddress 属性——我不希望“IPAddress ipValue”属性序列化为对象,我只希望它像裸字符串一样序列化,序列化 IPAddress 的字符串值
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-03-21
        • 2011-10-15
        • 1970-01-01
        • 1970-01-01
        • 2014-08-22
        • 1970-01-01
        相关资源
        最近更新 更多