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