Основная проблема здесь заключается в том, что документация для FileInfo
в netcore-2.2 просто неверна - FileInfo
на самом деле не помечена [Serializable]
в ядре .Net.Без [Serializable]
Json.NET будет пытаться сериализовать открытые свойства FileInfo
, а не его данные ISerializable
, что в конечном итоге приведет к исключению переполнения стека хотя бы для одного из свойств, FileInfo.Directory.Root.Root...
.Возвращенный JSON затем усекается в момент, когда генерируется исключение, так как сервер уже начал писать ответ в этот момент.
(На самом деле кажется, что FileInfo
занесен в черный список на ядре .Net, чтобы избежатьпереполнение стека, см. Проблема # 1541: исключение StackOverflowException при сериализации объекта DirectoryInfo в ядре dotnet 2 . Вместо этого выдается пользовательское исключение.)
Чтобы подтвердить ошибку документации, ссылка Источник для .Net core (зеркальный здесь ) показывает, что FileInfo
должен быть объявлен следующим образом (хотя он объявлен как partial
, он, кажется, имеет только один файл):
// Class for creating FileStream objects, and some basic file management
// routines such as Delete, etc.
public sealed partial class FileInfo : FileSystemInfo
{
В то время как справочный источник для полной платформы показывает следующее:
// Class for creating FileStream objects, and some basic file management
// routines such as Delete, etc.
[Serializable]
[ComVisible(true)]
public sealed class FileInfo: FileSystemInfo
{
Без атрибута [Serializable]
Json.NET будет игнорировать интерфейс ISerializable
в базовом классе.как описано в примечаниях к выпуску Json.NET 11 :
- Изменение - типы, которые реализуют ISerializable, но не имеют [SerializableAttribute], являютсяне сериализовано с использованием ISerializable
Итак, что можно сделать?Одной из возможностей может быть создание пользовательского преобразователя контракта , который вынуждает сериализовать FileInfo
с использованием интерфейса ISerializable
:
public class FileInfoContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (objectType == typeof(FileInfo))
{
return CreateISerializableContract(objectType);
}
var contract = base.CreateContract(objectType);
return contract;
}
}
Настройте преобразователь контракта, как показано, например, в Настройка JsonConvert.DefaultSettings asp net core 2.0 не работает должным образом .
Другой возможностью будет создание custom JsonConverter
для FileInfo
, который сериализует и десериализует те же свойства, что и полный каркас:
public class ISerializableJsonConverter<T> : JsonConverter where T : ISerializable
{
// Simplified from
// - JsonSerializerInternalReader.CreateISerializable()
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L1708
// - JsonSerializerInternalWriter.SerializeISerializable()
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs#L837
// By James Newton-King http://james.newtonking.com/
// Not implemented:
// PreserveReferencesHandling, TypeNameHandling, ReferenceLoopHandling, NullValueHandling
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
SerializationInfo serializationInfo = new SerializationInfo(objectType, new JsonFormatterConverter(serializer));
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
serializationInfo.AddValue((string)reader.Value, JToken.ReadFrom(reader.ReadToContentAndAssert()));
break;
default:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}
return Activator.CreateInstance(objectType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { serializationInfo, serializer.Context }, serializer.Culture);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var serializable = (ISerializable)value;
SerializationInfo serializationInfo = new SerializationInfo(value.GetType(), new FormatterConverter());
serializable.GetObjectData(serializationInfo, serializer.Context);
writer.WriteStartObject();
foreach (SerializationEntry serializationEntry in serializationInfo)
{
writer.WritePropertyName(serializationEntry.Name);
serializer.Serialize(writer, serializationEntry.Value);
}
writer.WriteEndObject();
}
}
public static partial class JsonExtensions
{
public static JsonReader ReadToContentAndAssert(this JsonReader reader)
{
return reader.ReadAndAssert().MoveToContentAndAssert();
}
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
internal class JsonFormatterConverter : IFormatterConverter
{
//Adapted and simplified from
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/FormatterConverter.cs
// By James Newton-King http://james.newtonking.com/
JsonSerializer serializer;
public JsonFormatterConverter(JsonSerializer serializer)
{
this.serializer = serializer;
}
private T GetTokenValue<T>(object value)
{
JValue v = (JValue)value;
return (T)System.Convert.ChangeType(v.Value, typeof(T), CultureInfo.InvariantCulture);
}
public object Convert(object value, Type type)
{
if (!(value is JToken))
{
throw new ArgumentException("Value is not a JToken.", "value");
}
return ((JToken)value).ToObject(type, serializer);
}
public object Convert(object value, TypeCode typeCode)
{
if (value is JValue)
{
value = ((JValue)value).Value;
}
return System.Convert.ChangeType(value, typeCode, CultureInfo.InvariantCulture);
}
public bool ToBoolean(object value)
{
return GetTokenValue<bool>(value);
}
public byte ToByte(object value)
{
return GetTokenValue<byte>(value);
}
public char ToChar(object value)
{
return GetTokenValue<char>(value);
}
public DateTime ToDateTime(object value)
{
return GetTokenValue<DateTime>(value);
}
public decimal ToDecimal(object value)
{
return GetTokenValue<decimal>(value);
}
public double ToDouble(object value)
{
return GetTokenValue<double>(value);
}
public short ToInt16(object value)
{
return GetTokenValue<short>(value);
}
public int ToInt32(object value)
{
return GetTokenValue<int>(value);
}
public long ToInt64(object value)
{
return GetTokenValue<long>(value);
}
public sbyte ToSByte(object value)
{
return GetTokenValue<sbyte>(value);
}
public float ToSingle(object value)
{
return GetTokenValue<float>(value);
}
public string ToString(object value)
{
return GetTokenValue<string>(value);
}
public ushort ToUInt16(object value)
{
return GetTokenValue<ushort>(value);
}
public uint ToUInt32(object value)
{
return GetTokenValue<uint>(value);
}
public ulong ToUInt64(object value)
{
return GetTokenValue<ulong>(value);
}
}
, а затем добавляет new ISerializableJsonConverter<FileInfo>()
к JsonSerializerSettings.Converters
.
Примечания: