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

时间:2014-06-29 01:26:06

标签: c# 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"}}

有谁知道怎么做?谢谢! (P.S.我在一个大型毛茸茸的遗留.NET代码库上使用Json序列化,因此我无法真正更改任何现有类型,但我可以对它们进行扩充/因子/装饰以促进Json序列化。)

6 个答案:

答案 0 :(得分:23)

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

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

答案 1 :(得分:1)

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方法。)

答案 2 :(得分:0)

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

我从中借用了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 :(得分:0)

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

方法1:

下面的示例显示了如何使用成员级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"
 }
]

方法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);
}

希望这有帮助。

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

答案 4 :(得分:0)

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

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

(PS。有人可以帮我清理源代码格式吗?)


var settings = new JsonSerializerSettings { Converters = new List {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]);
    }
}</code>

答案 5 :(得分:-1)

根据您可以花费的努力程度以及对现有课程更改的容忍度,有几种不同的方法可以解决这个问题。

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