La mejor forma de manejar esta situación es utilizar un archivo JsonConverter
.
Antes de llegar al convertidor, necesitaremos definir una clase para deserializar los datos. Para la Categories
propiedad que puede variar entre un solo elemento y una matriz, defínalo como y márquelo List<string>
con un [JsonConverter]
atributo para que JSON.Net sepa usar el convertidor personalizado para esa propiedad. También recomendaría usar [JsonProperty]
atributos para que las propiedades de los miembros puedan recibir nombres significativos independientemente de lo que esté definido en JSON.
class Item
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public int Timestamp { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
[JsonProperty("category")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Categories { get; set; }
}
Así es como implementaría el convertidor. Observe que hice el convertidor genérico para que pueda usarse con cadenas u otros tipos de objetos según sea necesario.
class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Aquí hay un programa corto que demuestra el convertidor en acción con sus datos de muestra:
class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""email"": ""[email protected]"",
""timestamp"": 1337966815,
""category"": [
""newuser"",
""transactional""
],
""event"": ""open""
},
{
""email"": ""[email protected]"",
""timestamp"": 1337966815,
""category"": ""olduser"",
""event"": ""open""
}
]";
List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);
foreach (Item obj in list)
{
Console.WriteLine("email: " + obj.Email);
Console.WriteLine("timestamp: " + obj.Timestamp);
Console.WriteLine("event: " + obj.Event);
Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
Console.WriteLine();
}
}
}
Y finalmente, aquí está el resultado de lo anterior:
email: [email protected]
timestamp: 1337966815
event: open
categories: newuser, transactional
email: [email protected]
timestamp: 1337966815
event: open
categories: olduser
Violín: https://dotnetfiddle.net/lERrmu
EDITAR
Si necesita ir al revés, es decir, serializar, manteniendo el mismo formato, puede implementar el WriteJson()
método del convertidor como se muestra a continuación. (Asegúrese de eliminar la CanWrite
anulación o cambiarla para que vuelva true
, o de lo contrario WriteJson()
nunca se llamará).
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<T> list = (List<T>)value;
if (list.Count == 1)
{
value = list[0];
}
serializer.Serialize(writer, value);
}
Violín: https://dotnetfiddle.net/XG3eRy
DeserializeObject
llamada si usa el[JsonConverter]
atributo en la propiedad de la lista en su clase, como se muestra en la respuesta anterior. Si no usa el atributo, entonces, sí, necesitará pasar el convertidor aDeserializeObject
.List<T>
en el convertidor ayT[]
cambie.Count
a.Length
. dotnetfiddle.net/vnCNgZEstuve trabajando en esto durante mucho tiempo y gracias a Brian por su respuesta. ¡Todo lo que estoy agregando es la respuesta de vb.net !:
luego en tu clase:
Espero que esto te ahorre algo de tiempo
fuente
Como una variación menor de la gran respuesta de Brian Rogers , aquí hay dos versiones modificadas de
SingleOrArrayConverter<T>
.En primer lugar, aquí hay una versión que funciona para todos
List<T>
para todos los tiposT
que no son en sí mismos una colección:Se puede utilizar de la siguiente manera:
Notas:
El convertidor evita la necesidad de precargar todo el valor JSON en la memoria como una
JToken
jerarquía.El convertidor no se aplica a listas cuyos elementos también se serializan como colecciones, p. Ej.
List<string []>
El
canWrite
argumento booleano pasado al constructor controla si volver a serializar listas de un solo elemento como valores JSON o como matrices JSON.El convertidor
ReadJson()
utiliza elexistingValue
if preasignado para admitir el llenado de miembros de la lista de solo obtención.En segundo lugar, aquí hay una versión que funciona con otras colecciones genéricas como
ObservableCollection<T>
:Luego, si su modelo está usando, digamos, an
ObservableCollection<T>
para algunosT
, podría aplicarlo de la siguiente manera:Notas:
SingleOrArrayListConverter
, elTCollection
tipo debe ser de lectura / escritura y tener un constructor sin parámetros.Demostración de violín con pruebas unitarias básicas aquí .
fuente
Tuve un problema muy similar. Mi solicitud de Json era completamente desconocida para mí. Yo solo lo sabía.
Habrá un objectId en él y algunos pares de valores clave anónimos Y matrices.
Lo usé para un modelo EAV que hice:
Mi solicitud JSON:
Mi Clase i definí:
y ahora que quiero deserializar atributos desconocidos con su valor y matrices en él, mi convertidor se ve así:
Así que ahora, cada vez que obtengo un AnonymObject, puedo recorrer el Diccionario y cada vez que aparece mi Bandera "ValueDummyForEAV", cambio a la lista, leo la primera línea y divido los valores. Después de eso, elimino la primera entrada de la lista y continúo con la iteración del Diccionario.
Tal vez alguien tenga el mismo problema y pueda usar esto :)
Saludos Andre
fuente
Puede usar un
JSONConverterAttribute
tal como se encuentra aquí: http://james.newtonking.com/projects/json/help/Suponiendo que tienes una clase que se parece a
Decoraría la propiedad de la categoría como se ve aquí:
fuente
Para manejar esto, debe usar un JsonConverter personalizado. Pero probablemente ya lo tenías en mente. Solo está buscando un convertidor que pueda usar de inmediato. Y esto ofrece algo más que una solución para la situación descrita. Doy un ejemplo con la pregunta formulada.
Cómo usar mi convertidor:
Coloque un atributo JsonConverter encima de la propiedad.
JsonConverter(typeof(SafeCollectionConverter))
Y este es mi convertidor:
Y este convertidor usa la siguiente clase:
¿Qué hace exactamente? Si coloca el atributo del convertidor, el convertidor se utilizará para esta propiedad. Puede usarlo en un objeto normal si espera una matriz json con 1 o sin resultado. O lo usa en un lugar
IEnumerable
donde espera un objeto json o una matriz json. (Sepa que unarray
-object[]
- es unIEnumerable
) Una desventaja es que este convertidor solo se puede colocar encima de una propiedad porque cree que puede convertir todo. Y ten cuidado . Astring
también es unIEnumerable
.Y ofrece más que una respuesta a la pregunta: si busca algo por id, sabe que obtendrá una matriz con uno o ningún resultado. El
ToObjectCollectionSafe<TResult>()
método puede manejar eso por ti.Esto se puede usar para Single Result vs Array usando JSON.net y maneja un solo elemento y una matriz para la misma propiedad y puede convertir una matriz en un solo objeto.
Hice esto para solicitudes REST en un servidor con un filtro que devolvió un resultado en una matriz, pero quería recuperar el resultado como un solo objeto en mi código. Y también para una respuesta de resultado de OData con resultado expandido con un elemento en una matriz.
Diviértete con eso.
fuente
Encontré otra solución que puede manejar la categoría como cadena o matriz usando object. De esta forma no necesito estropear el serializador json.
Por favor, échale un vistazo si tienes tiempo y dime lo que piensas. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
Se basa en la solución en https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ pero también agregué la conversión de fecha de la marca de tiempo, actualicé las variables para reflejar modelo de SendGrid actual (y las categorías hechas funcionar).
También creé un controlador con autenticación básica como opción. Vea los archivos ashx y los ejemplos.
¡Gracias!
fuente