2011-11-28 4 views
16

Deserializo algunas propiedades a Dictionary<string, object>.¿Cómo cambio el tipo predeterminado para deserialización numérica?

Cuando deserializo algunos json, rellena los objetos Dictionary con Int64 en lugar de Int32. Me gustaría que eligiera Int32 como el valor predeterminado sabiendo que podría tener números de JavaScript que se desbordarían en la conversión. Lanzar una excepción en ese caso sería completamente aceptable.

¿Hay alguna manera de lograr eso? Espero algunos buenos atributos o una interfaz conveniente que podría implementarse y agregarse al JsonSerializer. Y me temo que tengo que profundizar en las profundidades de Json.NET.

Básicamente me gustaría tener alguna manera de controlar los tipos conocidos de los objetos para que pudiera conseguir Int32 's en lugar de Int64 y DateTimes en lugar de Strings.

+0

Comentarios ordenaban: OP es consciente de la opción POCO, pero no quiere hacer eso –

Respuesta

18

Hasta donde yo sé, no hay una forma incorporada de hacerlo.

Hubo un issue sobre este tema, pero se ha cerrado. Algunos comentarios de la autora sobre el tema:

Json.NET por defecto lee valores enteros como Int64 porque no hay manera de saber si el valor debe ser Int32 o Int64, Int64 y es menos probable que se desborde. Para una propiedad mecanografiada, el deserializador sabe convertir el Int64 a Int32, pero como su propiedad está sin tipo, obtiene un Int64. [...] Es la forma en que Json.NET tiene que funcionar.

La solución más sencilla sería la de Coure ser cambiar el tipo de Dictionary<string, int>, pero supongo que no sólo leyendo los valores numéricos y por lo tanto está atascado con object.

Otra opción sería la de utilizar ya sea manualmente Serialization Callbacks y convertir esos Int64 s a Int32 o crear su propio Contract ResolverJsonConverter y directamente controlar la (des) serialización.


Editar: he creado un pequeño ejemplo para ser más específico.

Aquí es un conversor muy básico que sólo funciona con su diccionario specifc:

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(); 
    } 
} 

También se puede usar atributos para decorar los miembros con su convertidor o pass it as parameter a una (des) método de serialización.He aquí un ejemplo en el que he utilizado un atributo:

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

Y aquí está el código que utiliza para probar la aplicación:

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(); 
    } 
} 

Sin el convertidor, el resultado sería:

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

y con el convertidor es:

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

Lo que una respuesta totalmente impresionante. La clase Int32Converter es exactamente lo que estaba buscando. No voy a usarlo porque he cambiado la forma en que hago estas cosas, pero en ese momento esta hubiera sido la respuesta perfecta. :) – Mithon

+0

Esto funciona bien cuando tiene valores enteros en el diccionario. Sin embargo, si tiene valores enteros en los objetos en el diccionario de lo que no funciona. Alguna sugerencia para esto por favor? Funciona si el diccionario tiene {{"First", 1}, {"Second", 2}} No funciona si Dictionary tiene {{"First", {Age: "1"}}, {"Second ", {Edad:" 2 "}} –

+0

@HamidShahid depende de cómo se deserialice. Si posee el objeto en el Diccionario (el que tiene el campo 'Edad '), puede escribir un convertidor para él en lugar de para el diccionario. Sin embargo, si se trata de varios objetos diferentes, no lo recomendaría. Puede crear un convertidor numérico genérico como [aquí] (http://stackoverflow.com/questions/17745866/how-can-i-restore-the-int-deserialization-behavior-after-upgrading-json-net). Tenga en cuenta que mi respuesta es bastante antigua y que puede haber una solución incorporada por ahora. – enzi

1

Estoy aceptando la respuesta de Enzi ya que es lo que estaba pidiendo.

Sin embargo, desde entonces he cambiado mi estrategia.

En este momento estoy deserializando un ChangeSet<T> que en lugar del diccionario tiene un objeto fuertemente tipado Entity (T) con los cambios. También tiene un List<string> con los nombres de propiedad de las propiedades que estaban presentes en el json entrante. Luego llené esa lista durante la deserialización usando un MediaFormatter personalizado. De esta forma obtengo un objeto fuertemente tipado y corrijo la deserialización de todas las propiedades, y sé de la lista qué propiedades debo establecer en mi colección de T cuando quiero hacer mi operación por lotes.

De esta manera, básicamente uso mis entidades como DTO sin tener que tener una gran cantidad de DTO diferentes para diferentes operaciones por lotes. Es bastante elegante si lo digo yo mismo. :)

1

Trate

var variable = Convert.ToInt32(object) 

Iterar el Dictionary<string,object> una vez y volver a escribir su object con este Int32, o hacer la conversión Int32 cada vez que lea el object.

1

Esto funciona bien para mí:

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; 
    } 
} 
+0

Esta respuesta fue mucho más fácil para ponerse en contacto con la anulación de JsonConvert y para agregar más funcionalidad. Gracias –

Cuestiones relacionadas