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 становится вложенным объектом, ex:
// json = {"someString":"Some String","ipValue":{"value":"192.168.1.2"}}
Кто-нибудь знает, как это сделать? Благодарю! (P.S. Я закрепил сериализацию Json на большой волосатой старой кодовой базе .NET, поэтому я не могу реально изменить какие-либо существующие типы, но я могу расширять/множиться/украшать их, чтобы облегчить сериализацию Json.)
Ответы
Ответ 1
Вы можете справиться с этим, используя пользовательский 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"}
Ответ 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.)
Ответ 3
Это ответ на Настроить NewtonSoft.Json для сериализации объектов значений в отношении объектов значений в 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);
}
}
}
Ответ 4
С Cinchoo ETL - библиотека с открытым исходным кодом для разбора/записи файлов JSON, вы можете управлять сериализацией каждого члена объекта через ValueConverter или с механизмом обратного вызова.
Метод 1:
В приведенном ниже примере показано, как сериализовать "SomeOuterObject" с помощью ValueConverters уровня Member
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);
}
Надеюсь, что это поможет.
Отказ от ответственности: я являюсь автором библиотеки.
Ответ 5
Существует несколько различных способов подхода к этому в зависимости от уровня усилий, который вы можете затратить, и толерантности к изменениям существующих классов.
Один из подходов состоит в том, чтобы определить ваши классы как DataContract и явно идентифицировать элементы в классе как DataMembers. Netwonsoft распознает и использует эти атрибуты в своей сериализации. Подход к этому подходу состоит в том, что классы теперь будут сериализованы с использованием других подходов, которые используют сериализацию datacontract.
[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"
}