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"}}
有谁知道如何做到这一点? 谢谢! (PS我在大型毛茸茸的遗留.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"}
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方法。)
对于DDD中的值对象,这是针对值对象序列化自定义NewtonSoft.Json的答案。 但是这个问题被标记为重复,我不认为这是完全正确的。
我从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 { new ValueObjectConverter() } }; var json = JsonConvert.SerializeObject(given, jsonSerializerSettings); var result = JsonConvert.DeserializeObject(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 ConstructorArgumenTypes = new ConcurrentDictionary(); 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); } } }
使用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("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("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); }
希望这可以帮助。
免责声明:我是图书馆的作者。
这是一个简单值对象的generics转换类,我计划在下次更新Activout.RestClient时包含它。 作为对象的“简单值对象”具有:
- 没有默认构造函数
- 名为Value的公共属性
- 构造函数采用与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)); }
公共覆盖对象ReadJson(JsonReader reader,Type objectType,object existingValue, JsonSerializer序列化器) { 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)返回false; var valueProperty = GetValueProperty(objectType); if(valueProperty == null)返回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" }
另一种方法是创建自己的JsonConverterinheritance器,它可以准确地序列化您需要的内容,而无需修改类的内部:
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" }