У меня проблемы с десериализацией перечислений - PullRequest
1 голос
/ 05 июня 2019

Я использую NLog и отправляю объект LogEventInfo по проводам, мне не принадлежит этот объект, и поэтому я не могу украсить его [JsonConverter(typeof(StringEnumConverter))], или, может быть, я могу, и я просто не знаю как

Я попробовал следующий код, и он не помогает

var strategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy();
var sec = new StringEnumConverter(strategy, false);

//TEMP Hard code serialization and message routing for just logs
LogEventInfo info = Newtonsoft.Json.JsonConvert.DeserializeObject<LogEventInfo>(msg.Body, sec);

У кого-нибудь есть удача получить перечисления для десериализации, и перечисление находится в классе, который вам не принадлежит?

Вот сообщение:

{
   "date":"2019-06-04 21:48:24.0753",
   "level":"Error",
   "message":"{\"ApplicationId\":1390760,\"AppStatus\":\"#PG2\",\"Status\":400,\"ErrorCode\": 1053 }",
   "properties":"ResponseBody={\"ApplicationId\":1390760,\"AppStatus\":\"#PG2\",\"Status\":400,\"ErrorCode\": 1053 }|GroupId=392934|ApplicationId=1390760|Status=400",
   "callsite":"HFD.Enterprise.Logging.Tests.LogTest.LoadTest",
   "logger":"WebApiLog",
   "machinename":"BRANDONHOSTVS"
}

А вот и исключение:

Newtonsoft.Json.JsonSerializationException: Error converting value "Error" to type 'NLog.LogLevel'. Path 'level', line 1, position 72. ---> System.InvalidCastException: Invalid cast from 'System.String' to 'NLog.LogLevel'.
   at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
   at System.String.System.IConvertible.ToType(Type type, IFormatProvider provider)
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)
   --- End of inner exception stack trace ---

1 Ответ

4 голосов
/ 07 июня 2019

Ваша основная проблема в том, что LogEventInfo.Level это не перечисление , это класс LogLevel, который реализует IConvertible и имеет набор глобальных статических стандартных значений:

public sealed class LogLevel : IComparable, IEquatable<LogLevel>, IConvertible
{
    /// <summary>
    /// Gets all the available log levels (Trace, Debug, Info, Warn, Error, Fatal, Off).
    /// </summary>
    public static IEnumerable<LogLevel> AllLevels => allLevels;

Добавление StringEnumConverter не поможет вам (де) сериализовать такое свойство.

Более того, кажется, что NLog не предоставляет пользовательский TypeConverter для LogLevel. Если я вызываю TypeDescriptor.GetConverter(typeof(LogLevel)), возвращаемое значение является экземпляром конвертера по умолчанию System.ComponentModel.TypeConverter, что означает, что Json.NET не может преобразовать сериализованное строковое значение LogLevel обратно в экземпляр LogLevel.

Тем не менее, ваш образец JSON представляет Level как "level": "Error", и поэтому отправляющая система, должно быть, использовала какой-то custom JsonConverter для LogLevel, который сериализует только LogLevel.Name , Мы можем легко создать такой JsonConverter следующим образом:

public class LogLevelConverter : JsonConverter<LogLevel>
{
    public override LogLevel ReadJson(JsonReader reader, Type objectType, LogLevel existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        switch (reader.MoveToContentAndAssert().TokenType)
        {
            case JsonToken.Null:
                return null;
            case JsonToken.String:
                return LogLevel.FromString((string)reader.Value);
            default:
                throw new JsonSerializationException(string.Format("Unknown token {0}", reader.TokenType));
        }
    }

    public override void WriteJson(JsonWriter writer, LogLevel value, JsonSerializer serializer)
    {
        var logLevel = (LogLevel)value;
        writer.WriteValue(logLevel.Name);
    }
}

public static partial class JsonExtensions
{
    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;
    }
}

А затем используйте его следующим образом:

var seq = new LogLevelConverter();
var info = Newtonsoft.Json.JsonConvert.DeserializeObject<LogEventInfo>(msg.Body, seq);

Демонстрационная скрипка здесь .

...