SMAPI/Mods/Automate/Common/StringEnumArrayConverter.cs

154 lines
5.9 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace Pathoschild.Stardew.Common
{
/// <summary>A variant of <see cref="StringEnumConverter"/> which represents arrays in JSON as a comma-delimited string.</summary>
internal class StringEnumArrayConverter : StringEnumConverter
{
/*********
** Fields
*********/
/// <summary>Whether to return null values for missing data instead of an empty array.</summary>
public bool AllowNull { get; set; }
/*********
** Public methods
*********/
/// <summary>Get whether this instance can convert the specified object type.</summary>
/// <param name="type">The object type.</param>
public override bool CanConvert(Type type)
{
if (!type.IsArray)
return false;
Type elementType = this.GetElementType(type);
return elementType != null && base.CanConvert(elementType);
}
/// <summary>Read a JSON representation.</summary>
/// <param name="reader">The JSON reader from which to read.</param>
/// <param name="valueType">The value type.</param>
/// <param name="rawValue">The raw value of the object being read.</param>
/// <param name="serializer">The calling serializer.</param>
public override object ReadJson(JsonReader reader, Type valueType, object rawValue, JsonSerializer serializer)
{
// get element type
Type elementType = this.GetElementType(valueType);
if (elementType == null)
throw new InvalidOperationException("Couldn't extract enum array element type."); // should never happen since we validate in CanConvert
// parse
switch (reader.TokenType)
{
case JsonToken.Null:
return this.GetNullOrEmptyArray(elementType);
case JsonToken.StartArray:
{
string[] elements = JArray.Load(reader).Values<string>().ToArray();
object[] parsed = elements.Select(raw => this.ParseOne(raw, elementType)).ToArray();
return this.Cast(parsed, elementType);
}
case JsonToken.String:
{
string value = (string)JToken.Load(reader);
if (string.IsNullOrWhiteSpace(value))
return this.GetNullOrEmptyArray(elementType);
object[] parsed = this.ParseMany(value, elementType).ToArray();
return this.Cast(parsed, elementType);
}
default:
return base.ReadJson(reader, valueType, rawValue, serializer);
}
}
/// <summary>Write a JSON representation.</summary>
/// <param name="writer">The JSON writer to which to write.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
writer.WriteNull();
else if (value is IEnumerable list)
{
string[] array = (from object element in list where element != null select element.ToString()).ToArray();
writer.WriteValue(string.Join(", ", array));
}
else
base.WriteJson(writer, value, serializer);
}
/*********
** Private methods
*********/
/// <summary>Get the underlying array element type (bypassing <see cref="Nullable"/> if necessary).</summary>
/// <param name="type">The array type.</param>
private Type GetElementType(Type type)
{
if (!type.IsArray)
return null;
type = type.GetElementType();
if (type == null)
return null;
type = Nullable.GetUnderlyingType(type) ?? type;
return type;
}
/// <summary>Parse a string into individual values.</summary>
/// <param name="input">The input string.</param>
/// <param name="elementType">The enum type.</param>
private IEnumerable<object> ParseMany(string input, Type elementType)
{
string[] values = input.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string value in values)
yield return this.ParseOne(value, elementType);
}
/// <summary>Parse a string into one value.</summary>
/// <param name="input">The input string.</param>
/// <param name="elementType">The enum type.</param>
private object ParseOne(string input, Type elementType)
{
return Enum.Parse(elementType, input, ignoreCase: true);
}
/// <summary>Get <c>null</c> or an empty array, depending on the value of <see cref="AllowNull"/>.</summary>
/// <param name="elementType">The enum type.</param>
private Array GetNullOrEmptyArray(Type elementType)
{
return this.AllowNull
? null
: Array.CreateInstance(elementType, 0);
}
/// <summary>Create an array of elements with the given type.</summary>
/// <param name="elements">The array elements.</param>
/// <param name="elementType">The array element type.</param>
private Array Cast(object[] elements, Type elementType)
{
if (elements == null)
return null;
Array result = Array.CreateInstance(elementType, elements.Length);
Array.Copy(elements, result, result.Length);
return result;
}
}
}