Json.net 如何将对象序列化为值



我已经仔细研究了文档,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 序列化。

您可以使用

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"}

这是 Customise NewtonSoft.Json for Value Object 序列化的答案,涉及 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);
        }
    }
}
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 方法。

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

  1. 无默认构造函数
  2. 名为"值"的公共属性
  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]);
    }
}

有几种不同的方法可以解决这个问题,具体取决于您能够花费的工作量和对现有类更改的容忍度。

一种方法是将类定义为 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"
}

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

方法一:

下面的示例演示如何使用成员级值转换器序列化"某个外部对象">

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"
 }
]

方法2:

这是将值转换器回调挂接到"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);
}

希望这有帮助。

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

相关内容

  • 没有找到相关文章