A melhor maneira de lidar com essa situação é usar um custom JsonConverter
.
Antes de chegarmos ao conversor, precisaremos definir uma classe para desserializar os dados. Para a Categories
propriedade que pode variar entre um único item e uma matriz, defina-a como um List<string>
e marque-a com um [JsonConverter]
atributo para que JSON.Net saiba como usar o conversor personalizado para essa propriedade. Eu também recomendaria usar [JsonProperty]
atributos para que as propriedades do membro possam receber nomes significativos, independentemente do que está definido no 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; }
}
Aqui está como eu implementaria o conversor. Observe que tornei o conversor genérico para que possa ser usado com strings ou outros tipos de objetos, conforme necessário.
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();
}
}
Aqui está um pequeno programa que demonstra o conversor em ação com seus dados de amostra:
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();
}
}
}
E, finalmente, aqui está o resultado do acima:
email: [email protected]
timestamp: 1337966815
event: open
categories: newuser, transactional
email: [email protected]
timestamp: 1337966815
event: open
categories: olduser
Fiddle: https://dotnetfiddle.net/lERrmu
EDITAR
Se você precisar fazer o contrário, ou seja, serializar, mantendo o mesmo formato, você pode implementar o WriteJson()
método do conversor conforme mostrado abaixo. (Certifique-se de remover a CanWrite
substituição ou alterá-la para retornar true
, ou então WriteJson()
nunca será chamado.)
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);
}
Fiddle: https://dotnetfiddle.net/XG3eRy
DeserializeObject
chamada se usar o[JsonConverter]
atributo na propriedade list em sua classe, conforme mostrado na resposta acima. Se você não usar o atributo, então, sim, você precisará passar o conversor paraDeserializeObject
.List<T>
no conversor paraT[]
e altere.Count
para.Length
. dotnetfiddle.net/vnCNgZEu estava trabalhando nisso há muito tempo e agradeço a Brian por sua resposta. Tudo o que estou adicionando é a resposta do vb.net !:
então em sua classe:
Espero que isso te economize algum tempo
fonte
Como uma variação menor da ótima resposta de Brian Rogers , aqui estão duas versões ajustadas de
SingleOrArrayConverter<T>
.Em primeiro lugar, aqui está uma versão que funciona para todos,
List<T>
para cada tipoT
que não seja uma coleção:Ele pode ser usado da seguinte maneira:
Notas:
O conversor evita a necessidade de pré-carregar todo o valor JSON na memória como uma
JToken
hierarquia.O conversor não se aplica a listas cujos itens também são serializados como coleções, por exemplo
List<string []>
O
canWrite
argumento booleano passado para o construtor controla se as listas de elemento único devem ser re-serializadas como valores JSON ou como matrizes JSON.O conversor
ReadJson()
usa oexistingValue
if pré-alocado para oferecer suporte ao preenchimento de membros da lista get-only.Em segundo lugar, aqui está uma versão que funciona com outras coleções genéricas, como
ObservableCollection<T>
:Então, se seu modelo estiver usando, digamos, um
ObservableCollection<T>
para algunsT
, você pode aplicá-lo da seguinte maneira:Notas:
SingleOrArrayListConverter
, oTCollection
tipo deve ser de leitura / gravação e ter um construtor sem parâmetros.Demonstração brinca com testes de unidade básicos aqui .
fonte
Tive um problema muito semelhante. Minha solicitação Json era completamente desconhecida para mim. Eu apenas sabia.
Haverá um objectId nele e alguns pares de valores de chave anônimos E matrizes.
Eu usei para um modelo EAV que fiz:
Minha solicitação JSON:
Minha classe eu defini:
e agora que desejo desserializar atributos desconhecidos com seu valor e matrizes, meu conversor fica assim:
Portanto, agora, sempre que obtenho um AnonymObject, posso iterar no Dicionário e, sempre que houver meu sinalizador "ValueDummyForEAV", mudo para a lista, leio a primeira linha e divido os valores. Depois disso, excluo a primeira entrada da lista e prossigo com a iteração do Dicionário.
Talvez alguém tenha o mesmo problema e possa usar isso :)
Atenciosamente Andre
fonte
Você pode usar um
JSONConverterAttribute
como encontrado aqui: http://james.newtonking.com/projects/json/help/Presumindo que você tenha uma classe que parece
Você decoraria a propriedade da categoria como visto aqui:
fonte
Para lidar com isso, você deve usar um JsonConverter personalizado. Mas provavelmente você já tinha isso em mente. Você está apenas procurando um conversor que possa usar imediatamente. E isso oferece mais do que apenas uma solução para a situação descrita. Dou um exemplo com a pergunta feita.
Como usar meu conversor:
Coloque um Atributo JsonConverter acima da propriedade.
JsonConverter(typeof(SafeCollectionConverter))
E este é meu conversor:
E este conversor usa a seguinte classe:
O que isso faz exatamente? Se você colocar o atributo conversor, o conversor será usado para esta propriedade. Você pode usá-lo em um objeto normal se esperar um array json com 1 ou nenhum resultado. Ou use-o em um
IEnumerable
objeto onde espera um objeto json ou um array json. (Saiba que umarray
-object[]
- é umIEnumerable
) Uma desvantagem é que este conversor só pode ser colocado acima de uma propriedade porque ele pensa que pode converter tudo. E esteja avisado . Astring
também é umIEnumerable
.E oferece mais do que uma resposta à pergunta: se você pesquisar algo por id, você sabe que obterá um array de volta com um ou nenhum resultado. O
ToObjectCollectionSafe<TResult>()
método pode cuidar disso para você.Isso pode ser usado para Resultado Único vs Matriz usando JSON.net e manipula um único item e uma matriz para a mesma propriedade e pode converter uma matriz em um único objeto.
Fiz isso para solicitações REST em um servidor com um filtro que retornou um resultado em uma matriz, mas queria obter o resultado de volta como um único objeto em meu código. E também para uma resposta de resultado OData com resultado expandido com um item em uma matriz.
Divirta-se com isso.
fonte
Eu encontrei outra solução que pode lidar com a categoria como string ou array usando objeto. Assim não preciso atrapalhar o serializador json.
Por favor, dê uma olhada se você tiver tempo e me diga o que você pensa. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
É baseado na solução em https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/, mas também adicionei a conversão de data de carimbo de data / hora, atualizei as variáveis para refletir modelo SendGrid atual (e categorias feitas funcionar).
Também criei um manipulador com autenticação básica como opção. Veja os arquivos ashx e os exemplos.
Obrigado!
fonte