Сериализуемое перечисление строк JSON в C # с универсальным JsonConverter

Aug 17 2020

Как можно StringEnumerationизменить в приведенном ниже коде универсальный класс, чтобы ConsensusStateне потребовался JsonConverterатрибут, позволяющий использовать экземпляры класса, как если бы они были сериализуемыми перечислениями со связанными строковыми значениями?

Я использую, StringEnumerationчтобы разрешить проверку экземпляров объектов ConsensusStateagains stringв условных операторах или выводить их непосредственно на консоль как stringзначения, а также иметь возможность использовать их как свойства в Serializableклассах. Сериализация производится с использованием System.Text.Json.Serialization.

Вот классы:

/// <summary>Consensus state returned by the server.</summary>
[Serializable]
[JsonConverter(typeof(StringEnumerationConverter<ConsensusState>))]
public class ConsensusState : StringEnumeration
{
    /// <summary>Connecting.</summary>
    public static readonly ConsensusState Connecting = new ConsensusState("connecting");
    /// <summary>Syncing blocks.</summary>
    public static readonly ConsensusState Syncing = new ConsensusState("syncing");
    /// <summary>Consensus established.</summary>
    public static readonly ConsensusState Established = new ConsensusState("established");

    private ConsensusState(string value) : base(value) { }
}

/// <summary>JsonConverter used in string enumeration serialization.</summary>
public class StringEnumerationConverter<T> : JsonConverter<T>
{
    /// <summary>Read the string value.</summary>
    /// <param name="reader">Reader to access the encoded JSON text.</param>
    /// <param name="typeToConvert">Type of the object to deserialize.</param>
    /// <param name="options">Options for the deserialization.</param>
    /// <returns>Underlying string enumeration type.</returns>
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return (T)Activator.CreateInstance(typeof(T), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { reader.GetString() }, null, null);
    }

    /// <summary>Write the string value.</summary>
    /// <param name="writer">Writer to encode the JSON text.</param>
    /// <param name="value">Object to serialize.</param>
    /// <param name="options">Options for the serialization.</param>
    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WriteStringValue((string)typeof(T).GetProperty("Value").GetValue(value, null));
    }
}

/// <summary>Abstract base class used in string enumerations.</summary>
public abstract class StringEnumeration
{
    /// <summary>Associated value.</summary>
    public string Value { get; set; }

    /// <summary>Initializes the enumeration from a string.</summary>
    /// <param name="value">The associated value.</param>
    public StringEnumeration(string value) { Value = value; }

    /// <summary>Implicit conversion to string.</summary>
    /// <param name="obj">And StringEnumeration object.</param>
    /// <returns>string object.</returns>
    public static implicit operator string(StringEnumeration obj)
    {
        return obj.Value;
    }

    /// <summary>Get the string associated value.</summary>
    /// <returns>string object.</returns>
    public override string ToString()
    {
        return Value;
    }

    /// <summary>Test whether a StringEnumeration is equal to another object.</summary>
    /// <param name="a">StringEnumeration object.</param>
    /// <param name="b">Another object.</param>
    /// <returns>true if the two objects are equal.</returns>
    public static bool operator ==(StringEnumeration a, object b)
    {
        var other = b as StringEnumeration;
        if (other is null)
        {
            return false;
        }
        return a.Value == other.Value;
    }

    /// <summary>Test whether a StringEnumeration is different to other object.</summary>
    /// <param name="a">StringEnumeration object.</param>
    /// <param name="b">Another object.</param>
    /// <returns>true if the two objects are different.</returns>
    public static bool operator !=(StringEnumeration a, object b)
    {
        var other = b as StringEnumeration;
        if (other is null)
        {
            return true;
        }
        return a.Value != other.Value;
    }

    /// <summary>Test whether a StringEnumeration is equal to another object.</summary>
    /// <param name="obj">Another object.</param>
    /// <returns>true if the objects are equal.</returns>
    public override bool Equals(object obj)
    {
        var other = obj as StringEnumeration;
        if (other is null)
        {
            return false;
        }
        return Value == other.Value;
    }

    /// <summary>Get the hash code of the associated value.</summary>
    /// <returns>An integer value representing the hash of the associated value.</returns>
    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }
}

Ответы

1 rraallvv Aug 19 2020 at 05:01

Реализация StringEnumerationниже позволяет подклассу вести себя так, как если бы это было перечисление строк, а также имеет некоторые улучшения качества жизни, такие как возможность указывать значение, связанное со строкой, для каждого поля с помощью атрибута JsonStringValue().

/// <summary>Attribute to assign the string value of fields in StringEnumeration.</summary>
[AttributeUsage(AttributeTargets.Field)]
public class JsonStringValue : Attribute
{
    /// <summary>String value.</summary>
    public string Value;

    /// <summary>Initializes the Attribute instance to a given value.</summary>
    public JsonStringValue(string value)
    {
        Value = value;
    }
}

/// <summary>JsonConverter used in string enumeration serialization.</summary>
public class StringEnumerationConverter : JsonConverter<StringEnumeration>
{
    /// <summary>Whether a type is a subclass of <c>StringEnumeration</c>.</summary>
    /// <param name="typeToConvert">Type to check.</param>
    /// <returns>True if is a subclass.</returns>
    public override bool CanConvert(Type typeToConvert)
    {
        return typeof(StringEnumeration).IsAssignableFrom(typeToConvert);
    }

    /// <summary>Read the string value.</summary>
    /// <param name="reader">Reader to access the encoded JSON text.</param>
    /// <param name="typeToConvert">Type of the object to deserialize.</param>
    /// <param name="options">Options for the deserialization.</param>
    /// <returns>Underlying string enumeration type.</returns>
    public override StringEnumeration Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var obj = (StringEnumeration)FormatterServices.GetUninitializedObject(typeToConvert);
        obj.Value = reader.GetString();
        return obj;
    }

    /// <summary>Write the string value.</summary>
    /// <param name="writer">Writer to encode the JSON text.</param>
    /// <param name="value">Object to serialize.</param>
    /// <param name="options">Options for the serialization.</param>
    public override void Write(Utf8JsonWriter writer, StringEnumeration value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value);
    }
}

/// <summary>Abstract base class used in string enumerations.</summary>
public abstract class StringEnumeration
{
    /// <summary>Associated value.</summary>
    public string Value { get; set; }

    /// <summary>Initializes all static fields in subclasses.</summary>
    static StringEnumeration()
    {
        var types = typeof(StringEnumeration).Assembly.GetTypes();
        foreach (var type in types)
        {
            if (type.BaseType == typeof(StringEnumeration))
            {
                var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
                foreach (var field in fields)
                {
                    var attribute = field.GetCustomAttribute<JsonStringValue>();
                    var name = attribute is null ? field.Name : attribute.Value;
                    var value = (StringEnumeration)FormatterServices.GetUninitializedObject(type);
                    value.Value = name;
                    field.SetValue(null, value);
                }
            }
        }
    }

    /// <summary>Implicit conversion to string.</summary>
    /// <param name="obj">And StringEnumeration object.</param>
    /// <returns>string object.</returns>
    public static implicit operator string(StringEnumeration obj)
    {
        if (obj is null)
        {
            return null;
        }
        return obj.ToString();
    }

    /// <summary>Get the string associated value.</summary>
    /// <returns>string object.</returns>
    public override string ToString()
    {
        return Value;
    }

    /// <summary>Test whether a StringEnumeration is equal to another object.</summary>
    /// <param name="a">StringEnumeration object.</param>
    /// <param name="b">Another object.</param>
    /// <returns>true if the two objects are equal.</returns>
    public static bool operator ==(StringEnumeration a, object b)
    {
        if (a is null)
        {
            return b is null;
        }
        return !(b is null) && a.Value == b.ToString();
    }

    /// <summary>Test whether a StringEnumeration is different to other object.</summary>
    /// <param name="a">StringEnumeration object.</param>
    /// <param name="b">Another object.</param>
    /// <returns>true if the two objects are different.</returns>
    public static bool operator !=(StringEnumeration a, object b)
    {
        return !(a == b);
    }

    /// <summary>Test whether a StringEnumeration is equal to another object.</summary>
    /// <param name="obj">Another object.</param>
    /// <returns>true if the objects are equal.</returns>
    public override bool Equals(object obj)
    {
        return this == obj;
    }

    /// <summary>Get the hash code of the associated value.</summary>
    /// <returns>An integer value representing the hash of the associated value.</returns>
    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }
}

Вот как это использовать:

/// <summary>Consensus state returned by the server.</summary>
[Serializable]
[JsonConverter(typeof(StringEnumerationConverter))]
public class ConsensusState : StringEnumeration
{
    /// <summary>Connecting.</summary>
    [JsonStringValue("connecting")]
    public static ConsensusState Connecting;
    /// <summary>Syncing blocks.</summary>
    [JsonStringValue("syncing")]
    public static ConsensusState Syncing;
    /// <summary>Consensus established.</summary>
    [JsonStringValue("established")]
    public static ConsensusState Established;
}