【问题标题】:How do I change the default Type for Numeric deserialization?如何更改数字反序列化的默认类型?
【发布时间】:2022-03-13 08:21:51
【问题描述】:

我将一些属性反序列化为Dictionary<string, object>

当我反序列化一些 json 时,它会使用 Int64 对象而不是 Int32 填充 Dictionary。我希望它选择Int32 作为默认值,因为我知道我可以拥有在转换时会溢出的javascript Numerics。在这种情况下抛出异常是完全可以接受的。

有什么方法可以实现吗?我希望有一些不错的属性或方便的界面可以实现并添加到JsonSerializer。而且我担心我必须深入到 Json.NET 的深处。

基本上我想有一些方法来控制对象的已知类型,这样我就可以得到Int32's 而不是Int64DateTimes 而不是Strings

【问题讨论】:

  • 评论整理:OP 知道 POCO 选项但不想这样做

标签: c# json.net


【解决方案1】:

据我所知,没有内置的方法可以做到这一点。

有一个关于这个主题的issue,但它已被关闭。 作者关于这个问题的一些cmets:

Json.NET 默认将整数值读取为 Int64,因为无法知道该值应该是 Int32 还是 Int64,并且 Int64 不太可能溢出。对于类型化的属性,反序列化器知道将 Int64 转换为 Int32,但是因为您的属性是无类型的,所以您得到的是 Int64。 [...] 这正是 Json.NET 的工作方式。

最简单的解决方案当然是将类型更改为Dictionary<string, int>,但我想您不仅在阅读数字,因此被object 卡住了。

另一种选择是使用 Serialization Callbacks 并将这些 Int64s 手动转换为 Int32 或创建自己的 Contract Resolver JsonConverter 并直接控制 (de-)序列化。


编辑:我创建了一个更具体的小例子。

这是一个非常基本的转换器,仅适用于您的特定字典:

public class Int32Converter : JsonConverter {
    public override bool CanConvert(Type objectType) {
        // may want to be less concrete here
        return objectType == typeof(Dictionary<string, object>);
    }

    public override bool CanWrite {
        // we only want to read (de-serialize)
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        // again, very concrete
        Dictionary<string, object> result = new Dictionary<string, object>();
        reader.Read();

        while (reader.TokenType == JsonToken.PropertyName) {
            string propertyName = reader.Value as string;
            reader.Read();

            object value;
            if (reader.TokenType == JsonToken.Integer)
                value = Convert.ToInt32(reader.Value);      // convert to Int32 instead of Int64
            else
                value = serializer.Deserialize(reader);     // let the serializer handle all other cases
            result.Add(propertyName, value);
            reader.Read();
        }

        return result;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        // since CanWrite returns false, we don't need to implement this
        throw new NotImplementedException();
    }
}

您可以使用属性通过转换器或pass it as parameter 来装饰成员(反)序列化方法。这是我使用属性的示例:

    [JsonObject]
public class MyObject {
    [JsonConverter(typeof(Int32Converter))]
    public Dictionary<string, object> Properties { get; set; }
}

这是我用来测试实现的代码:

class Program {
    static void Main(string[] args) {
        MyObject test = new MyObject();
        test.Properties = new Dictionary<string, object>() { { "int", 15 }, { "string", "hi" }, { "number", 7 } };
        Print("Original:", test);

        string json = JsonConvert.SerializeObject(test);
        Console.WriteLine("JSON:\n{0}\n", json);

        MyObject parsed = JsonConvert.DeserializeObject<MyObject>(json);
        Print("Deserialized:", parsed);
    }

    private static void Print(string heading, MyObject obj) {
        Console.WriteLine(heading);
        foreach (var kvp in obj.Properties)
            Console.WriteLine("{0} = {1} of {2}", kvp.Key, kvp.Value, kvp.Value.GetType().Name);
        Console.WriteLine();
    }
}

没有转换器,结果将是:

Deserialized:
int = 15 of Int64
string = hi of String
number = 7 of Int64

使用转换器是:

Deserialized:
int = 15 of Int32
string = hi of String
number = 7 of Int32

【讨论】:

  • 多么棒的答案。 Int32Converter 类正是我想要的。我不会使用它,因为我已经改变了我做这些事情的方式,但当时这将是完美的答案。 :)
  • 当字典中有整数值时,这可以正常工作。但是,如果字典中的对象中有整数值,则它不起作用。请问对此有什么建议吗?如果 Dictionary 有 { {"First" , 1}, {"Second", 2} } 则有效 如果 Dictionary 有 { {"First", {Age: "1"}}, {"Second", {Age 则无效: "2"}}
  • @HamidShahid 取决于您如何反序列化。如果您拥有 Dictionary 中的对象(具有Age 字段的对象),则可以为它编写转换器而不是为字典编写转换器。但是,如果您要处理各种不同的对象,我不推荐它。您可以创建一个通用数字转换器,如here。请记住,我的答案现在已经很老了,现在可能有一个内置的解决方案。
  • @HamidShahid 我还没有找到任何内置的东西,但是受到这个答案的启发,我 wrote a more generalized converter 提出了一个类似的问题,可能会解决你的问题——重要的部分是检查类型是否为 @ 987654338@ 所以转换器将适用。
【解决方案2】:

试试

   var variable = Convert.ToInt32(object) 

迭代一次Dictionary&lt;string,object&gt; 并用这个Int32 重写它的object,或者在每次读取object 时进行Int32 转换。

【讨论】:

  • Json.NET 将数字反序列化为长框 (Int64)。您也可以将其拆箱并将其转换为 int:var variable = (int)(long)object; 但我认为您的解决方案更安全;)
【解决方案3】:

我接受 Enzi 的回答,因为这是我所要求的。

然而,从那以后我改变了我的策略。

现在我正在反序列化为 ChangeSet&lt;T&gt;,而不是字典,它有一个带有更改的强类型 Entity (T) 对象。它还有一个 List&lt;string&gt; ,其中包含传入 json 中存在的属性的属性名称。然后,我在反序列化期间使用自定义 MediaFormatter 填充该列表。这样我就得到了一个强类型对象和所有属性的正确反序列化,并且我从列表中知道当我想做批处理操作时应该在我的 T 集合上设置哪些属性。

这样,我基本上将我的实体用作 DTO,而不必为不同的批处理操作使用无数不同的 DTO。如果我自己这么说的话,那真是太时尚了。 :)

【讨论】:

    【解决方案4】:

    这对我来说效果很好:

    public class ParseNumbersAsInt32Converter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(long) || objectType == typeof(long?) || objectType == typeof(object);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value != null && reader.Value is long)
            {
                return Convert.ToInt32(reader.Value);
            }
            return reader.Value;
        }
    }
    

    【讨论】:

    • 这个答案更容易与覆盖 JsonConvert 取得联系并添加更多功能。谢谢
    【解决方案5】:

    我发现了这个问题,因为我遇到了同样的问题。我决定回去转换它们。

    var items = GetDataFile<Item[]>("Items");
    foreach(var m in items)
    {
        // Deserialized int becomes long if object type. I want int.
        foreach(var pk in m.Properties.Keys)
        {
            if(m.Properties[pk] is long)
            {
                m.Properties[pk] = Convert.ToInt32(m.Properties[pk]);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-07-01
      • 2023-01-30
      • 1970-01-01
      • 1970-01-01
      • 2010-11-18
      • 2014-09-30
      • 2018-08-20
      • 2014-09-27
      • 2017-01-09
      相关资源
      最近更新 更多