Runtime: Suporta DOM "dinâmico" e gravável

Criado em 29 mai. 2019  ·  47Comentários  ·  Fonte: dotnet/runtime

JsonSerializer.Parse(String, Type, JsonSerializerOptions) suporta o tipo de retorno ExpandoObject dinâmico?

Algo assim:
dynamic p = JsonSerializer.Parse(json, typeof(ExpandoObject));

area-System.Text.Json enhancement json-functionality-doc

Comentários muito úteis

Eu também tenho um caso de uso para isso - eu só quero chamar uma API REST com HttpClient e recuperar uma única propriedade da resposta. Não quero criar uma classe dedicada apenas para analisar a resposta ... Com JSON.NET eu poderia usar apenas "dinâmico" e acessar a propriedade de escolha.

Todos 47 comentários

Não, este recurso não é compatível neste momento, mas é algo que devemos considerar para o vNext. Você tem um exemplo de uso para motivar a solicitação de recurso?

Marcando como futuro.

System.NotSupportedException : The collection type 'System.Dynamic.ExpandoObject' is not supported.

@ahsonkhan GraphQL é um bom exemplo.

A especificação recomenda JSON, mas não está vinculada a nenhuma serialização específica na resposta.
Isso implica que o campo "dados" da resposta é do tipo: dinâmico. Como não pode ser inferido.
Sem ExpandoObject, a desserialização torna dinâmico um tipo de criação JSON. Portanto, o acesso a esses "dados" dinâmicos abstratos deve ser feito sabendo que na verdade essa dinâmica é um JToken.

Com ExpandoObject acho que poderíamos forçar o acesso dos "dados" dinâmicos como um objeto comum

@ahsonkhan outro exemplo é o Serviço de configuração em nosso projeto. Ele expõe a coleção como pontos de extremidade REST, que criam coleções no MongoDB (não é apenas um invólucro REST fictício, e as coleções restantes e a coleção mongo não têm mapeamento 1-1 exato, ele também afirma certas regras).

Portanto, em nosso projeto, precisamos de suporte dinâmico / ExpandoObject.

Também o usamos em outros microsserviços.

Também encontramos essa limitação. Nosso caso de uso é construir gradualmente um objeto dinâmico antes da serialização json. Voltei para o serializador Json.NET mais maduro.

Ei pessoal

qual é a caminhada agora?
Posso configurar qual usar?
@SidShetye você disse algo sobre voltar para um mais maduro, pode explicar, por favor?

@MickeyReznikov : consulte https://stackoverflow.com/questions/15455304/deserialize-a-property-as-an-expandoobject-using-json-net ou google “json.net expandoobject”

@MickeyReznikov , sua pergunta foi respondida? Eu acredito que @SidShetye significa voltar a usar Newtonsoft.Json para serializar objetos dinâmicos, uma vez que a biblioteca in-box (System.Text.Json) não tem suporte para eles ainda. No caso de aplicativos asp.net, você pode configurá-lo para AddNewtonsoftJson voltar.

Consulte https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio#jsonnet -support

Eu também tenho um caso de uso para isso - eu só quero chamar uma API REST com HttpClient e recuperar uma única propriedade da resposta. Não quero criar uma classe dedicada apenas para analisar a resposta ... Com JSON.NET eu poderia usar apenas "dinâmico" e acessar a propriedade de escolha.

De https://github.com/dotnet/corefx/issues/41472 por @ ghost1372 :

oi espero que este seja um bom lugar para fazer esta pergunta
Parece que não podemos desserializar objetos dinâmicos
usei esse código, mas não funcionou. Existe alguma maneira de fazer isso?

var objList = System.Text.Json.JsonSerializer.Deserialize<List<dynamic>>(json);

Em nossos muitos aplicativos, estamos controlando os campos de dados dos procedimentos armazenados e renderizando dinamicamente as páginas Lista e Lista de Pesquisa com jquery.jtable

Sem o suporte integrado do JsonSerializer para ExpandoObject, não podemos usar a serialização Json integrada do dotnet core

Compartilhamos o mesmo caso de uso já mencionado por @estiller e @SidShetye
Tivemos que voltar ao Json.NET nesse meio tempo.

É possível ter um marco mais próximo do _agora_ do que do futuro ? 🤔

ExpandoObject está na BCL há muuuuito tempo

Existe alguma alternativa para ExpandoObject?

@fatihyildizhan Não, não há alternativa.

mas escrevemos nosso próprio conversor ExpandoObject, você pode pegar a dica deste artigo ASP.NET Core 3.0: Custom JsonConverter para o novo System.Text.Json

Precisamos apenas da serialização, então apenas criamos a serialização

Fiquei surpreso que ainda não fosse compatível.

Movendo isso para 5.0.

Mudando para 5.0? Isso significa que temos que esperar pelo menos um ano? De volta ao JSON.Net, é isso.

5,0? uau, isso definitivamente é uma merda.

Para uma solução temporária feia, posso usar JsonDocument com conversor personalizado para ele, mas é IDisposable.

public sealed class EventObject
    {
        [JsonPropertyName("id")]
        public long Id
        {
            get; set;
        }

        [JsonPropertyName("eventData")]
        [JsonConverter(typeof(JsonDocumentConverter))]
        public System.Text.Json.JsonDocument EventData
        {
            get; set;
        }
    }

    internal sealed class JsonDocumentConverter
        : JsonConverter<System.Text.Json.JsonDocument>
    {
        public override JsonDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return JsonDocument.ParseValue(ref reader);
        }

        public override void Write(Utf8JsonWriter writer, JsonDocument value, JsonSerializerOptions options)
        {
            value.WriteTo(writer);
        }
    }

Não podemos mais usar a seguinte ação POST na presença de System.Text.Json:

[HttpPost]
public async Task<IActionResult> SubmitAsync(dynamic model)

Em vez disso, tivemos que usar o método a seguir, mas não havia como usar 'modelo' no código de back-end downstream:

[HttpPost]
public async Task<IActionResult> SubmitAsync(JsonElement model)

'modelo' é um objeto complexo e pode conter uma coleção de objetos e / ou outros objetos complexos aninhados. Para poder concluir um objeto dynamic de JsonElement , tivemos que chamar model.GetRawText () e fazer com que a Newtonsoft o decodificasse em um objeto dinâmico. Essa não é a maneira desejada porque o objetivo principal deste exercício era descomissionar o Newtonsoft.json do projeto.

Posso presumir que resolver este tíquete / problema implica uma solução para o nosso problema que estamos enfrentando? Parece ser um problema um pouco urgente para resolver, então pode ser resolvido mais cedo ou mais tarde?

/ cc @ahsonkhan @terrajobst

.NET Core 3.0 JsonSerializer.Deserialize para objeto dinâmico

Suporte JsonSerializer para ExpandoObject (medidas provisórias)
Eu sou novato, muitos lugares não são perfeitos, todos bem-vindos para modificar
.net Core3 sem suporte

Adicione o conversor Json

adicione usando:

  • using System.Text.Json;
  • using System.Text.Json.Serialization;

`` `C #
///


/// Temp Dynamic Converter
/// by: [email protected]
///

public class DynamicJsonConverter: JsonConverter
{
leitura dinâmica de substituição pública (ref leitor Utf8JsonReader,
Digite typeToConvert,
Opções JsonSerializerOptions)
{

        if (reader.TokenType == JsonTokenType.True)
        {
            return true;
        }

        if (reader.TokenType == JsonTokenType.False)
        {
            return false;
        }

        if (reader.TokenType == JsonTokenType.Number)
        {
            if (reader.TryGetInt64(out long l))
            {
                return l;
            }

            return reader.GetDouble();
        }

        if (reader.TokenType == JsonTokenType.String)
        {
            if (reader.TryGetDateTime(out DateTime datetime))
            {
                return datetime;
            }

            return reader.GetString();
        }

        if (reader.TokenType == JsonTokenType.StartObject)
        {
            using JsonDocument documentV = JsonDocument.ParseValue(ref reader);
            return ReadObject(documentV.RootElement);
        }
        // Use JsonElement as fallback.
        // Newtonsoft uses JArray or JObject.
        JsonDocument document = JsonDocument.ParseValue(ref reader);
        return document.RootElement.Clone();
    }

    private object ReadObject(JsonElement jsonElement)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        foreach (var obj in jsonElement.EnumerateObject())
        {
            var k = obj.Name;
            var value = ReadValue(obj.Value);
            expandoObject[k] = value;
        }
        return expandoObject;
    }
    private object? ReadValue(JsonElement jsonElement)
    {
        object? result = null;
        switch (jsonElement.ValueKind)
        {
            case JsonValueKind.Object:
                result = ReadObject(jsonElement);
                break;
            case JsonValueKind.Array:
                result = ReadList(jsonElement);
                break;
            case JsonValueKind.String:
                //TODO: Missing Datetime&Bytes Convert
                result = jsonElement.GetString();
                break;
            case JsonValueKind.Number:
                //TODO: more num type
                result = 0;
                if (jsonElement.TryGetInt64(out long l))
                {
                    result = l;
                }
                break;
            case JsonValueKind.True:
                result = true;
                break;
            case JsonValueKind.False:
                result = false;
                break;
            case JsonValueKind.Undefined:
            case JsonValueKind.Null:
                result = null;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        return result;
    }

    private object? ReadList(JsonElement jsonElement)
    {
        IList<object?> list = new List<object?>();
        foreach (var item in jsonElement.EnumerateArray())
        {
            list.Add(ReadValue(item));
        }
        return list.Count == 0 ? null : list;
    }
    public override void Write(Utf8JsonWriter writer,
        object value,
        JsonSerializerOptions options)
    {
       // writer.WriteStringValue(value.ToString());
    }
}
## How to Use?

```C#
var serializerOptions = new JsonSerializerOptions
{
    Converters = { new DynamicJsonConverter() }
};
return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);

@tchivs , sua solução funcionou para mim; mas como a propriedade Converters é somente leitura, tive que fazer algo assim:
c# var serializerOptions = new JsonSerializerOptions(); serializerOptions.Converters.Add(new DynamicJsonConverter()); return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);

Tente usar o tipo JsonElement:

public JsonElement MyProperty {get; set;}

@tchivs , fiz algumas modificações em seu código - retrabalhando-o para que use tipos de "projeção" gerados dinamicamente (tendo um subconjunto de propriedades de um tipo base), em vez de ExpandoObject. Publiquei o código (em um projeto de console de amostra) aqui:

Vou testar essa abordagem com objetos mais complicados e em vários cenários (por exemplo, desserialização de objetos dinâmicos usados ​​para corrigir entidades EF Core existentes e totalmente digitadas; desserialização de objetos json e matrizes para casos de teste). Avise-me se você achar isso útil ou se encontrar alguma coisa problemática.

Obrigado pela solução alternativa da comunidade. É surpreendente que a Microsoft não seja capaz de lançar esse recurso em um prazo razoável. Trabalhar com respostas GraphQL sem objeto dinâmico de algum tipo leva a muito código prolixo e feio. Ou mesmo apenas verificando a existência de propriedades profundamente aninhadas.

Li toda a sequência de comentários e a maioria está focada na desserialização. Estou enfrentando um problema em que a serialização de objetos dinâmicos aparentemente "falha" silenciosamente. Na tentativa de reproduzir meu cenário, criei a seguinte reprodução mínima:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text.Json;

namespace SampleConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic d = new CustomDynamicObject();
            d.AProperty = 10;
            var s = JsonSerializer.Serialize(d);

            Console.WriteLine(s);
            Console.Read();
        }
    }

    class CustomDynamicObject : DynamicObject 
    {
        private readonly IDictionary<string, object> properties = new Dictionary<string, object>();

        public override bool TryGetMember(GetMemberBinder binder, out object result) => properties.TryGetValue(binder.Name, out result);

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            properties[binder.Name] = value;
            return true;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            foreach (var item in properties.Keys)
            {
                yield return item;
            }
        }
    }
}

Quando executado s é {} , então a serialização não falha, mas produz um objeto json vazio.

Este é o problema certo? ou devo aumentar / seguir um diferente?

Este é o problema certo? ou devo aumentar / seguir um diferente?

Esta é a questão certa. Não há necessidade de abrir um para as duas metades do mesmo recurso. Este problema é sobre como adicionar suporte para objeto expando (o que significa, para ambos os lados, serializar e desserializar).

Eu encontrei este problema hoje - queria dizer que JsonElement funcionaria bem se não dependesse de JsonDocument descartados. Uma maneira de contornar esse problema é implementar um destruidor para JsonDocument forma que seu descarte possa ser adiado para um momento posterior - assim que todos os objetos JsonElement forem coletados.

Eu também precisava de um objeto dinâmico. No entanto, ainda não foi implementado. Minha solução foi usar um dicionário.
JsonSerializer.Deserialize<Dictionary<string, string>>(response)
Em seguida, procure a chave de que preciso :)

Apenas para minha própria sanidade - o problema tem como escopo o 5.0 especificamente para suporte a ExpandoObject ou a capacidade de desserializar em um objeto dinâmico? A conversa aqui não parece corresponder ao título do problema, e estou definitivamente precisando do último. No meu caso, estou desserializando uma dinâmica aninhada, especificamente List<Dictionary<string, dynamic>> . Retornado para Newtonsoft por agora :(

só queria compartilhar alguns mods de açúcar de sintaxe do c # 8 do código útil @tchivs :)

/// <summary>
/// Temp Dynamic Converter with c# 8
/// by:[email protected]
/// </summary>
public class DynamicJsonConverter : JsonConverter<dynamic>
{
    public override dynamic Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => reader.TokenType switch
        {
            JsonTokenType.True => true,
            JsonTokenType.False => false,
            JsonTokenType.Number => reader.TryGetInt64(out long l) ? 1 : reader.GetDouble(),
            JsonTokenType.String => reader.TryGetDateTime(out DateTime datetime) ? datetime.ToString() : reader.GetString(),
            JsonTokenType.StartObject =>  ReadObject(JsonDocument.ParseValue(ref reader).RootElement),
            // Use JsonElement as fallback.
                _ =>JsonDocument.ParseValue(ref reader).RootElement.Clone()
        };

    private object ReadObject(JsonElement jsonElement)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        foreach (var obj in jsonElement.EnumerateObject())
        {
            var k = obj.Name;
            var value = ReadValue(obj.Value);
            expandoObject[k] = value;
        }
        return expandoObject;
    }
    private object? ReadValue(JsonElement jsonElement)
        =>
         jsonElement.ValueKind switch
        {
            JsonValueKind.Object => ReadObject(jsonElement),
            JsonValueKind.Array => ReadList(jsonElement),
            JsonValueKind.String => jsonElement.GetString(),
            JsonValueKind.Number =>  jsonElement.TryGetInt64(out long l) ? 1 :0,
            JsonValueKind.True => true,
            JsonValueKind.False =>false,
            JsonValueKind.Undefined => null,
            JsonValueKind.Null => null,
                _ => throw new ArgumentOutOfRangeException()
        };

    private object? ReadList(JsonElement jsonElement)
    {
        var list = new List<object?>();
        jsonElement.EnumerateArray().ToList().ForEach(j => list.Add(ReadValue(j)));
        return list.Count == 0 ? null : list;
    }

    public override void Write(Utf8JsonWriter writer,
        object value,
        JsonSerializerOptions options)
        {
        // writer.WriteStringValue(value.ToString());
        }
}

@ rs38 obrigado pelo código aqui, era exatamente o que eu precisava. Queria apontar para uma mudança muito sutil, mas importante, necessária. As duas linhas que analisam o tipo "Número" estão incorretas em sua versão compactada:

JsonTokenType.Number => reader.TryGetInt64(out long l) ? 1 : reader.GetDouble(),

deveria estar

JsonTokenType.Number => reader.TryGetInt64(out long l) ? l : reader.GetDouble(),

JsonValueKind.Number => jsonElement.TryGetInt64(out long l) ? 1 :0,

deveria estar

JsonValueKind.Number => jsonElement.TryGetInt64(out long l) ? l :0,

@layomia Isso não é sério. O suporte deveria ter sido feito há muito tempo (na verdade, System.Text.Json não deveria ter sido lançado sem ele na minha opinião)! E nem temos prazo!

Meu cenário é sobre o CosmosDB. Estou fazendo consultas usando o novo .NET SDK que usa este JsonSerializer e, como é um banco de dados sem esquema, não quero criar uma classe para cada projeção dos dados do banco de dados que preciso fazer (há um monte de consultas diferentes) .
Preciso dos resultados das consultas como listas de objetos dinâmicos.

@ SocVi100 Não coloque esperanças na Microsoft para este. Melhor ficar com o Json.net da Newtonsoft. @layomia esvaziou todas as esperanças de uma integração em um futuro próximo. Parece que eles realmente não se importam com as necessidades do desenvolvedor. Primeiro eles gritaram "esqueça o json.net, nós ajudamos você!" Felizmente, os desenvolvedores não se esqueceram do json.net ou similares.

porque você não contribui? implemente e coloque em um PR!

https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/adding-api-guidelines.md
https://github.com/dotnet/runtime/blob/master/docs/area-owners.md (Lead:
@ericstj |, proprietários: @layomia @steveharter @jozkee)

porque você não contribui? implemente e coloque em um PR!

Eu gostaria de poder.

Meu cenário é sobre o CosmosDB. Estou fazendo consultas usando o novo .NET SDK que usa este JsonSerializer e, como é um banco de dados sem esquema, não quero criar uma classe para cada projeção dos dados do banco de dados que preciso fazer (há um monte de consultas diferentes) .
Preciso dos resultados das consultas como listas de objetos dinâmicos.

É possível configurar um serializador diferente, usando CosmosClientBuilder.WithCustomSerializer .

Aqui está um exemplo: CosmosJsonNetSerializer

Não foi possível ajustar esse recurso no 5.0. Tivemos que priorizar junto com o resto do trabalho do recurso 5.0, e este não se encaixou. FWIW isso estava muito perto da linha de corte, portanto, o movimento tardio.

@layomia esvaziou todas as esperanças de uma integração em um futuro próximo. Parece que eles realmente não se importam com as necessidades do desenvolvedor. Primeiro eles gritaram "esqueça o json.net, nós ajudamos você!" Felizmente, os desenvolvedores não se esqueceram do json.net ou similares.

@RobbyDeLaet podemos tentar manter esta discussão construtiva? Estamos tentando fazer o nosso melhor para responder aos principais recursos solicitados pelos clientes, mantendo os princípios de design. Com relação à paridade de recursos com Newtonsoft.Json, aqui está o que temos a dizer .

System.Text.Json se concentra principalmente em desempenho, segurança e conformidade com os padrões. Para alguns cenários, System.Text.Json não tem funcionalidade interna, mas existem soluções alternativas recomendadas. Se o seu aplicativo depende de um recurso ausente, considere apresentar um problema para descobrir se o suporte para o seu cenário pode ser adicionado.

Não temos como objetivo substituir o Newtonsoft.JSON. Se funcionar para você, continue a usá-lo. Faremos o nosso melhor para tornar System.Text.Json o mais útil possível, enquanto mantemos os ganhos que alcançamos em desempenho, segurança, conformidade de padrões e camadas. Com o tempo, esperamos fazer com que funcione para o maior número de pessoas possível. Estou ouvindo o interesse aqui e farei com que nos concentremos nisso no futuro.

Obrigado por suas sugestões. Eu finalmente fui pelo meio, usando o serializador Newtonsoft apenas para desserialização, mas ainda tenho que testar. Espero que não dure muito para obter suporte ExpandoObject no serializador padrão do CosmosDB para que eu possa me livrar do antigo.
@RobbyDeLaet , obrigado pelo seu comentário! Não tenho muitas expectativas sobre a implementação de qualquer coisa pela Microsoft, já me senti frustrado muitas vezes com isso. Como desenvolvedor freelance, esperei anos, literalmente, por recursos essenciais a serem implementados na infraestrutura do CosmosDB, EntityFramework e Identity, e eles fazem seu próprio caminho nos ignorando. Não importa quantos votos eles têm no UserVoice ou qualquer outra coisa. Suponho que tais recursos não sejam necessários para seus próprios desenvolvimentos, portanto, estão fora do escopo ...
O problema é sempre o mesmo: muito marketing = muitas expectativas

Para ajudar aqueles que precisam dessa funcionalidade, consulte os exemplos de código acima para ajudar a desbloquear:

  • De @tchivs (que usa ExpandoObject para objetos e List<object> para coleções)
  • De @denmitchell (que usa IL Emit para objetos em vez de ExpandoObject )

Para ajudar com os requisitos desse recurso, fornecerei um novo exemplo de conversor personalizado e o vincularei aqui quando estiver pronto. Depois disso, vou adicionar essa amostra à seção de solução alternativa da @tdykstra

Um detalhe ainda não discutido é que o suporte de dynamic requer uma referência ao grande conjunto System.Linq.Expressions.dll ; esta complicação pode significar que, no futuro, adicionaremos o conversor dinâmico em um novo assembly (por exemplo, System.Text.Json.Converters.dll ) para obter pagamento para jogar para aqueles que se preocupam com o tamanho da implantação (como um aplicativo independente ou um Blazor aplicativo cliente).

Apenas para minha própria sanidade - o problema tem como escopo o 5.0 especificamente para suporte a ExpandoObject ou a capacidade de desserializar em um objeto dinâmico? A conversa aqui não parece corresponder ao título do problema, e estou definitivamente precisando do último. No meu caso, estou desserializando uma dinâmica aninhada, especificamente List<Dictionary<string, dynamic>> . Retornado para Newtonsoft por agora :(

Agora estou testando uma função antiga que implementei para desserializar objetos aninhados e vendo o mesmo problema observado neste tópico com "valueKind".

Havia uma maneira de corrigir isso usando os novos pacotes neetonsoft.json? Porque o código acima funcionou para o objeto de nível raiz, mas não para os objetos aninhados.

@ericstj Desculpe , minha reação foi talvez um pouco negativa e dura demais, mas como @ SocVi100 , sou um desenvolvedor freelance e às vezes as frustrações levam a melhor. Mas pelo menos começamos uma discussão aqui. Obrigado pela sua resposta, agora recebemos pelo menos uma mensagem clara sobre o status desta solicitação.

Apenas para minha própria sanidade - o problema tem como escopo o 5.0, especificamente o suporte ExpandoObject ou a capacidade de desserializar em um objeto dinâmico

Suponho que a semântica desejada seja um tipo dinâmico para que, após a desserialização, você possa acessar todas as propriedades (incluindo aninhadas) de maneira tardia:

dynamic obj = JsonSerializer.Deserialize<dynamic>(json);
string s = obj.MyChildProperty.MyStringList[2];

e suponho que a semântica desejada também suporte ExpandoObject explicitamente:

ExpandoObject expando = JsonSerializer.Deserialize<ExpandoObject>(json);
dynamic obj = expando;
string s = obj.MyChildProperty.MyStringList[2];

Então, meu entendimento do escopo:

  • Deserialize<dynamic>(json) retorna uma primitiva, coleção ou objeto. Um objeto aqui provavelmente será ExpandoObject mas pode ser IDynamicMetaObjectProvider ou um tipo JITed dependendo da implementação. Isso é diferente de hoje, que sempre retorna JsonElement .
  • Deserialize<ExpandoObject>(json) (que implementa IDynamicMetaObjectProvider ) deve criar um ExpandoObject apropriado que, por sua vez, pode ser usado com dynamic . Isso é diferente de hoje, que só cria propriedades expando para as propriedades raiz e JsonElement instâncias para todas as propriedades aninhadas.
  • Serialize<ExpandoObect>() funciona conforme o esperado
  • Serialize<IDynamicMetaObjectProvider ()> pode ou não ser implementado dependendo se há cenários onde ExpandoObject não é usado.

Semântica em 3.0 - 5.0:

  • Serializar um ExpandoObject funciona porque ExpandoObject implementa IDictionary<string, object> e o STJ serializará corretamente cada object seja ele um primitivo, coleção ou objeto.
  • A desserialização em um tipo de ExpandoObject funciona, mas apenas as propriedades raiz são propriedades expando "adequadas"; quaisquer propriedades aninhadas serão JsonElement portanto, há inconsistência no modelo de programação, portanto, a desserialização de ExpandoObject deve ser evitada, a menos que um conversor personalizado seja usado para isso.

Opções para 3.0-5.0:

  • Escreva um conversor personalizado para ExpandoObject e \ ou object conforme mencionado nas postagens acima; Fornecerei um link em breve para um novo exemplo que mapeia idealmente para um recurso 6.0.
  • Se você apenas serializar, não desserializar, pode usar ExpandoObject (ou dynamic se o tipo for baseado em ExpandoObject ). Como a desserialização de ExpandoObject tem um problema de inconsistência hoje no STJ, ExpandoObject não é recomendada se a desserialização.
  • Use JsonElement vez de objetos dinâmicos. JsonElement não tenta "adivinhar" para qual tipo o JSON mapeia, então você deve ser explícito chamando GetString (), GetInt32 (), etc.

Além disso, dependendo da implementação, a implementação de um conversor customizado provavelmente significa que há algumas "suposições" sobre quais tipos de CLR o JSON mapeia durante a desserialização. Por exemplo, uma string JSON pode ser mapeada para DateTime ou string , um número JSON pode mapear para double ou long , e o O tipo de array JSON precisa ser determinado. Isso precisa ser verificado e comparado com a semântica da Newtonsoft. Isso também deve se alinhar ao tipo que é retornado quando uma propriedade é do tipo object (hoje é JsonElement ).

Aqui estão alguns exemplos de semântica do STJ 3.0 - 5.0:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;

namespace ConsoleApp
{
    class Program
    {
        const string ExpectedJson = "{\"A\":\"A\",\"B\":[1,2],\"C\":42,\"D\":\"2020-01-01T00:00:00\",\"E\":{\"A_Child\":\"A_Child\"}}";

        static void Main(string[] args)
        {
            DateTime dateTime = new DateTime(2020, 1, 1);

            dynamic myDynamicChild = new ExpandoObject();
            myDynamicChild.A_Child = "A_Child";

            dynamic myDynamic = new ExpandoObject();
            myDynamic.A = "A";
            myDynamic.B = new List<int>() { 1, 2 };
            myDynamic.C = 42;
            myDynamic.D = dateTime;
            myDynamic.E = myDynamicChild;

            // Verify we can call late-bound property.
            int c = myDynamic.C;
            Debug.Assert(c == 42);

            // STJ can serialize with ExpandoObject since it implements IDictionary<string, object>.
            string json = JsonSerializer.Serialize<ExpandoObject>(myDynamic);
            Debug.Assert(json == ExpectedJson);

            // Using 'dynamic' against backing ExpandoObject works.
            json = JsonSerializer.Serialize<dynamic>(myDynamic);
            Debug.Assert(json == ExpectedJson);

            // Deserialize with <dynamic>, <object> and <JsonElement>.
            // For 5.0, using one of these is recommended over ExpandoObject because the programming model will be
            // consistent for the root type and all nested types.
            // Using <JsonElement> makes it clear and non-abiguous.
            // Using <object> by default uses 'JsonElement', but can be overridden by a custom converter.
            // Using <dynamic> uses 'object' which uses 'JsonElement'.
            {
                dynamic d = JsonSerializer.Deserialize<dynamic>(json);
                VerifyJsonElement(d);

                try
                {
                    // We will get an exception here if we try to access a dynamic property since 'object' is deserialized
                    // as a JsonElement and not an ExpandoObject.
                    c = d.C;
                    Debug.Fail("Should have thrown Exception!");
                }
                catch (Exception ex)
                {
                    Debug.Assert(ex.Message == "'System.Text.Json.JsonElement' does not contain a definition for 'C'");
                }

                // Serializing with <object> creates a JsonElement by default (can be changed by a custom converter).
                object o = JsonSerializer.Deserialize<object>(json);
                VerifyJsonElement((JsonElement)o);

                // Serialize with explicit <JsonElement>.
                JsonElement e = JsonSerializer.Deserialize<JsonElement>(json);
                VerifyJsonElement(e);
            }

            // Deserialize with ExpandoObject. This creates an ExpandoObject with the root Type having Expando-properties
            // but the value of those properties will be JsonElement. All other nested properties\objects\collections will
            // also be JsonElement. Due to the inconsistency of having only root-level Expando-properties (such as A_Child),
            // deserializing as ExpandoObject is not recommended (unless a ExpandoObject custom converter is used).
            {
                // When STJ deserializes, it creates ExpandoObjects via IDictionary<string, object> where 'object' is JsonElement.
                ExpandoObject expando = JsonSerializer.Deserialize<ExpandoObject>(json);
                Debug.Assert(((IDictionary<string, object>)expando).Keys.Count == 5);
                myDynamic = expando;

                JsonElement jsonElement = myDynamic.A;
                Debug.Assert(jsonElement.GetString() == "A");

                jsonElement = myDynamic.B;
                Debug.Assert(jsonElement.EnumerateArray().Count() == 2);

                jsonElement = myDynamic.C;
                Debug.Assert(jsonElement.GetInt32() == 42);

                jsonElement = myDynamic.D;
                Debug.Assert(jsonElement.GetDateTime() == dateTime);

                jsonElement = myDynamic.E;
                // Here we have an inconsistency. Nested object property must use JsonElement (not a dynamic property).
                Debug.Assert(jsonElement.GetProperty("A_Child").GetString() == "A_Child");

                // Re-serialize works as expected.
                json = JsonSerializer.Serialize<ExpandoObject>(myDynamic);
                Debug.Assert(json == ExpectedJson);

                // Re-serialize works as expected; dynamic works here since backed by ExpandoObject in this example.
                json = JsonSerializer.Serialize<dynamic>(myDynamic);
                Debug.Assert(json == ExpectedJson);
            }

            void VerifyJsonElement(JsonElement elem)
            {
                // Verify JsonElement
                Debug.Assert(elem.GetProperty("A").GetString() == "A");
                Debug.Assert(elem.GetProperty("B").EnumerateArray().Count() == 2);
                Debug.Assert(elem.GetProperty("C").GetInt32() == 42);
                Debug.Assert(elem.GetProperty("D").GetDateTime() == dateTime);
                Debug.Assert(elem.GetProperty("E").GetProperty("A_Child").GetString() == "A_Child");

                // Re-serialize
                json = JsonSerializer.Serialize<dynamic>(elem);
                Debug.Assert(json == ExpectedJson);

                json = JsonSerializer.Serialize<JsonElement>(elem);
                Debug.Assert(json == ExpectedJson);
            }
        }
    }
}

@ rs38 Você pode corrigir seu trecho de código com base no que @ ryan-hollister-q2 apontou?

Conforme prometido, o PR fornecendo um exemplo de implementação dinâmica está em https://github.com/dotnet/runtime/pull/42097.

@ rs38 Você pode corrigir seu trecho de código com base no que @ ryan-hollister-q2 apontou?

aquele não era meu código, apenas compartilhei alguns trechos de @tchivs anteriormente neste tópico.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

chunseoklee picture chunseoklee  ·  3Comentários

jkotas picture jkotas  ·  3Comentários

bencz picture bencz  ·  3Comentários

omajid picture omajid  ·  3Comentários

yahorsi picture yahorsi  ·  3Comentários