【问题标题】:I'm having problems deserializing enumerations我在反序列化枚举时遇到问题
【发布时间】:2019-06-05 05:17:00
【问题描述】:

我正在使用 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 ---

【问题讨论】:

  • 看起来 "message" 是一个嵌入的双序列化 JSON 字符串,而 "properties" 包含嵌入的 JSON,周围有一些附加文本。这可能是问题吗?即使您不能修改该类,您也可以共享其参考源,即minimal reproducible example

标签: c# json.net nlog


【解决方案1】:

您的基本问题是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 似乎没有为LogLevel 提供自定义的TypeConverter。如果我调用TypeDescriptor.GetConverter(typeof(LogLevel)),则返回值是默认转换器System.ComponentModel.TypeConverter 的一个实例,这意味着Json.NET 无法将序列化的LogLevel 字符串值转换回LogLevel 实例。

尽管如此,您的 JSON 样本确实将 Level 表示为 "level": "Error",因此发送系统必须使用某种形式的 custom JsonConverter 来表示仅序列化 LogLevel.NameLogLevel。我们可以很容易地重新创建这样的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);

演示小提琴here.

【讨论】:

  • “NLog 似乎没有为 LogLevel 提供自定义的 TypeConverter”。 NLog 怎么能提供呢? (公关可能被接受;)
  • @Julian - 只需将一些适当的TypeConverterAttribute 添加到LogLevel,例如[TypeConverter(typeof(LogLevelTypeConverter))]。见c# how to implement type converter。完成后,Json.NET 将接收它,请参阅 Json.Net: Serialize/Deserialize property as a value, not as an object
  • @Julian - 但是,当前的实现有点奇怪。如果我做JsonConvert.SerializeObject(LogLevel.Info) 结果是"2",显然是因为((IConvertible)LogLevel.Info).ToType(typeof(string), CultureInfo.InvariantCulture)) 返回2。添加TypeConverter 会改变这种预先存在的(但可能是不受欢迎的)行为。见dotnetfiddle.net/3nzhLr
  • 仅供参考,NLog 4.6.5(今天发布)为此添加了一个 TypeConverter
猜你喜欢
  • 2016-12-15
  • 2021-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-19
  • 2019-05-31
  • 2015-07-26
  • 1970-01-01
相关资源
最近更新 更多