简体   繁体   English

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

[英]Json.net how to serialize object as value

I've pored through the docs, StackOverflow, etc., can't seem to find this...我仔细阅读了文档、StackOverflow 等,似乎找不到这个...

What I want to do is serialize/deserialize a simple value-type of object as a value, not an object, as so:我想要做的是将一个简单的值类型的对象序列化/反序列化为一个值,而不是一个对象,如下所示:

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);

What I want is for the json to serialize like this:我想要的是 json 像这样序列化:

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

Not where the ip becomes a nested object, ex:不是 ip 成为嵌套对象的地方,例如:

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

Does anyone know how to do this?有谁知道如何做到这一点? Thanks!谢谢! (PS I am bolting Json serialization on a large hairy legacy .NET codebase, so I can't really change any existing types, but I can augment/factor/decorate them to facilitate Json serialization.) (PS 我将 Json 序列化固定在一个庞大的遗留 .NET 代码库上,因此我无法真正更改任何现有类型,但我可以增加/分解/修饰它们以促进 Json 序列化。)

You can handle this using a custom JsonConverter for the IPAddress class.您可以使用IPAddress类的自定义JsonConverter处理此问题。 Here is the code you would need:这是您需要的代码:

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

Then, add a [JsonConverter] attribute to your IPAddress class and you're ready to go:然后,将[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()); 
    }
}

Here is a working demo:这是一个工作演示:

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

Output:输出:

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

This is a answer to Customise NewtonSoft.Json for Value Object serialisation , in regards to value objects in DDD.这是针对DDD 中的值对象自定义 NewtonSoft.Json for Value Object serialization的答案。 But that question is marked as duplicate to this one, which i don't think is completely true.但是这个问题被标记为与这个问题重复,我认为这不完全正确。

I borrowed the code for the ValueObjectConverter from https://github.com/eventflow/EventFlow , I have only done minor changes.我从https://github.com/eventflow/EventFlow借用了 ValueObjectConverter 的代码,我只做了很小的改动。

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);

You are serializing the whole IP address instance.您正在序列化整个 IP 地址实例。 Maybe just try to serialize the address as a string.也许只是尝试将地址序列化为字符串。 (This presumes that you have implemented the ToString-method.) (这假定您已经实现了 ToString 方法。)

With the Cinchoo ETL - an open source library to parsing / writing JSON files, you can control the serialization of each object member via ValueConverter or with callback mechanism.使用Cinchoo ETL - 一个解析/写入 JSON 文件的开源库,您可以通过 ValueConverter 或回调机制控制每个对象成员的序列化。

Method 1:方法一:

The sample below shows how to serialize 'SomeOuterObject' using member level ValueConverters下面的示例显示了如何使用成员级别的 ValueConverters 序列化“SomeOuterObject”

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

And the value converter is而价值转换器是

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();
    }
}

Finally to serialize the object to file最后将对象序列化到文件

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

And the output is输出是

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

Method 2:方法二:

This the alternative method to hook up value converter callback to 'ipValue' property.这是将值转换器回调连接到“ipValue”属性的替代方法。 This approach is lean and no need to create value converter for just this operation.这种方法是精益的,不需要为此操作创建价值转换器。

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

Hope this helps.希望这可以帮助。

Disclaimer: I'm the author of the library.免责声明:我是图书馆的作者。

Here is a class for generic conversion of simple value objects that I plan to include in the next update of Activout.RestClient.这是我计划在 Activout.RestClient 的下一次更新中包含的简单值对象的通用转换类。 A "simple value object" as an object that has: “简单值对象”作为具有以下特性的对象:

  1. No default constructor没有默认构造函数
  2. A public property named Value名为 Value 的公共属性
  3. A constructor taking the same type as the Value property采用与 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]);
    }
}

There are a couple of different ways to approach this depending on the level of effort you are able to expend and the tolerance for changes to existing classes.有几种不同的方法可以解决这个问题,具体取决于您能够花费的努力程度以及对现有类更改的容忍度。

One approach is to define your classes as DataContract and explicitly identify the elements within the class as DataMembers .一种方法是将您的类定义为DataContract并将类中的元素明确标识为DataMembers Netwonsoft recognizes and uses these attributes in its serialization. Netwonsoft 在其序列化中识别并使用这些属性。 The upside to this approach is that the classes will now be serializable using other approaches that use datacontract serialization.这种方法的好处是现在可以使用其他使用数据契约序列化的方法来序列化类。

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

Here is the code that I used to serialize (I may be using an older version since I didn't see the SerializeObject method):这是我用来序列化的代码(我可能使用的是旧版本,因为我没有看到 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());
            }
        }

Here is the output:这是输出:

{
  ipValue: "192.168.1.2"
}

Another approach is to create your own JsonConverter inheritor that can serialize exactly what you need without modifications to the internals of the class:另一种方法是创建您自己的 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();
    }
}

This class just writes the tostring value of the class along with the class name.这个类只是将类的 tostring 值与类名一起写入。 Changing the name can be accomplished through additional attributes on the class, which I have not shown.更改名称可以通过类上的其他属性来完成,我没有展示。

The class would then look like:该类将如下所示:

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

        public override string ToString()
        {
            return "192.168.1.2";
        }
    }

And the output is:输出是:

{
  IPAddress: "192.168.1.2"
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM