Runtime: Support "dynamic" and writable DOM

Created on 29 May 2019  ·  47Comments  ·  Source: dotnet/runtime

Does JsonSerializer.Parse(String, Type, JsonSerializerOptions) support for dynamic ExpandoObject return type?

Something like this:
dynamic p = JsonSerializer.Parse(json, typeof(ExpandoObject));

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

Most helpful comment

I also have a use-case for this - I just want to call a REST API with HttpClient and retrieve a single property from the response. I do not want to create a dedicated class just to parse the response... With JSON.NET I could just use "dynamic" and access the property of choice.

All 47 comments

No, this feature is not supported at this point but something we should consider for vNext. Do you have an example usage to motivate the feature request?

Marking as future.

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

@ahsonkhan GraphQL is a good example.

The spec recomends JSON but it is not tied to any specific serialization in the response.
This implys that the "data" field of the response is of the kind: dynamic. As it can't be infered.
Without ExpandoObject the deserialization makes dynamic a type of a JSON making. So accessing to that abstract dynamic "data" must be done knowing that in fact that dynamic is a JToken.

With ExpandoObject I think that we could enforce the access of the dynamic "data" like a common object

@ahsonkhan another example is Configuration Service in our project. It exposes collection like REST endpoints, which create collections in MongoDB (it is not just a dummy REST wrapper, and rest collections and mongo collection do not have exact 1-1 mapping, it also asserts certain rules).

So in our project we need dynamic/ExpandoObject support.

We use it in other microservices as well.

We also ran into this limitation. Our use case is gradually building a dynamic object before json serialization. Went back to the more mature Json.NET serializer.

Hey guys

whats the walk around for now?
May i configure which one to use?
@SidShetye you said something about went back to more mature one, may you explain please?

@MickeyReznikov, was your question answered? I believe @SidShetye meant going back to using Newtonsoft.Json for serializing dynamic objects since the in-box library (System.Text.Json) doesn't have support for those yet. In the case of asp.net apps, you can configure it to AddNewtonsoftJson back.

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

I also have a use-case for this - I just want to call a REST API with HttpClient and retrieve a single property from the response. I do not want to create a dedicated class just to parse the response... With JSON.NET I could just use "dynamic" and access the property of choice.

From https://github.com/dotnet/corefx/issues/41472 by @ghost1372:

hi I hope this is a good place to ask this question
It seems we can't deserialize dynamic objects
i used this code but not worked is there any way to do this?

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

In our many many applications we are controlling the fields of data from the stored procedures and dynamically render the List and Search List pages with jquery.jtable

Without JsonSerializer built-in support for ExpandoObject we cannot use dotnet core built-in Json Serialization

We do share the same use case as already mentioned by @estiller and @SidShetye
We had to switch back to Json.NET in the meantime.

Is it possible to have a milestone closer to _now_ than Future? 🤔

ExpandoObject's been in the BCL for a looooong time

Is there any alternative for ExpandoObject?

@fatihyildizhan No there is no alternate.

but we have written our own ExpandoObject converter, you can take the hint from this article ASP.NET Core 3.0: Custom JsonConverter for the new System.Text.Json

We need only the serialization, so we just create the serialization

I was surprised this isn't supported yet.

Moving this to 5.0.

Moving to 5.0? That means we have to wait for a year at least? Back to JSON.Net it is.

5.0? wow, that definitely sucks.

For temporal ugly workaround I can use JsonDocument with custom converter for it, but it is 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);
        }
    }

We are no longer able to use the following POST action in the presence of System.Text.Json:

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

Instead, we had to use the following method but there was no straight forward to use 'model' in the downstream backend code:

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

'model' is a complex object and it may contain a collection of objects and/or other nested complex objects. To be able to conclude a dynamic object out of JsonElement, we had to call model.GetRawText() and have Newtonsoft decode it into a dynamic object. This way is not the desired way because the main purpose of this exercise was to decommission Newtonsoft.json from the project.

Can I assume that addressing this ticket/issue implies a fix for our issue that we've been experiencing? It seems to be a bit urgent issue to address, so can it be addressed sooner than later?

/cc @ahsonkhan @terrajobst

.NET Core 3.0 JsonSerializer.Deserialize to dynamic object

JsonSerializer support for ExpandoObject(Interim measures)
I am newbie, many places are not perfect, welcome everyone to modify
.net Core3 no support

Add the Json Converter

add using:

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

```C#
///


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

public class DynamicJsonConverter : JsonConverter
{
public override dynamic Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{

        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, your solution worked for me; but since the Converters property is read-only, I had to do something like this:
c# var serializerOptions = new JsonSerializerOptions(); serializerOptions.Converters.Add(new DynamicJsonConverter()); return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);

Try using the JsonElement type:

public JsonElement MyProperty {get; set;}

@tchivs, I made some modifications to your code -- reworking it so that it uses dynamically generated "projection" types (having a subset of properties from a base type), rather than ExpandoObject. I posted the code (in a sample console project) here: EDennis.DynamicDeserialization.

I will be testing this approach with more complicated objects and under various scenarios (e.g., deserialization of dynamic objects used to patch existing, fully typed EF Core entities; deserialization of json objects and arrays for test cases). Let me know if you find this useful or if you see anything problematic.

Thanks for the community workaround. It's surprising Microsoft are unable to put out this feature in a sensible timeframe. Working with GraphQL responses without dynamic object of some sort leads to lots of verbose and ugly code. Or even just checking for existence of deeply nested properties.

I read through the thread of comments and most are focused on deserialization, I'm facing an issue where also serialization of dynamic objects apparently silently "fails". In an attempt to reproduce my scenario I came up with the following minimal repro:

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

When run s is {}, so serialization doesn't fail but yields an empty json object.

Is this the right issue? or should I raise/follow a different one?

Is this the right issue? or should I raise/follow a different one?

This is the right issue. No need to open one for the two halves of the same feature. This issue is about adding support for expando object (which means for both sides, serialize and deserialize).

I've met this issue today - wanted to say that JsonElement would work fine if it didn't depend on disposed JsonDocument. One way I can think of going around this issue is to implement a destructor for JsonDocument so it's disposal can be postponed to later time - once all JsonElement objects are collected.

I also needed a dynamic object. However, it's not implemented yet. My solution was to use a dictionary.
JsonSerializer.Deserialize<Dictionary<string, string>>(response)
And then lookup for the key I need :)

Just for my own sanity - is the issue scoped for 5.0 specifically ExpandoObject support or the ability to deserialize into a dynamic object? The conversation here doesn't all seem to match the issue title, and I'm definitely needing the latter. In my case I'm deserializing a nested dynamic, specifically List<Dictionary<string, dynamic>>. Switched back to Newtonsoft for now :(

just wanted to share some c# 8 syntax sugar mods of @tchivs helpful code :)

/// <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 thanks for the code here, was exactly what I needed. Wanted to point on a very subtle but important change needed. The two lines that parse the "Number" type are incorrect in your compressed version:

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

should be

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

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

should be

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

@layomia This is not serious. Support should have been done a long time ago (in fact System.Text.Json shouldn't have been launched without it in my opinion)! And we don't even have a deadline!

My scenario is about CosmosDB. I'm querying using the new .NET SDK wich uses this JsonSerializer and since it is a schemaless database I don't want to create a class for each projection of the database data I need to do (there are a bunch of different queries).
I need the results of the queries as lists of dynamic objects.

@SocVi100 Do not put your hopes on Microsoft for this one. Better stick with Newtonsoft's Json.net. @layomia has flushed all hopes for an near future integration. It seems they don't really care about the developer's needs. First they shouted "forget about json.net, we got you covered!" Luckily, developers didn't forget about json.net, or the alikes.

why don't you contribute? implement it and put in a 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 |, owners: @layomia @steveharter @jozkee)

why don't you contribute? implement it and put in a PR!

I wish I could.

My scenario is about CosmosDB. I'm querying using the new .NET SDK wich uses this JsonSerializer and since it is a schemaless database I don't want to create a class for each projection of the database data I need to do (there are a bunch of different queries).
I need the results of the queries as lists of dynamic objects.

It's possible to configure a different serializer, using CosmosClientBuilder.WithCustomSerializer.

Here is an example: CosmosJsonNetSerializer

We couldn't fit this feature into 5.0. We had to prioritize along with the rest of the 5.0 feature work, and this one didn't fit. FWIW this was very close to the cut line, thus the late move.

@layomia has flushed all hopes for an near future integration. It seems they don't really care about the developer's needs. First they shouted "forget about json.net, we got you covered!" Luckily, developers didn't forget about json.net, or the alikes.

@RobbyDeLaet can we try to keep this discussion constructive? We are trying to do our best to respond to top requested customer features while maintaining design principles. With respect to feature parity with Newtonsoft.Json, here's what we have to say.

System.Text.Json focuses primarily on performance, security, and standards compliance. It has some key differences in default behavior and doesn't aim to have feature parity with Newtonsoft.Json. For some scenarios, System.Text.Json has no built-in functionality, but there are recommended workarounds. For other scenarios, workarounds are impractical. If your application depends on a missing feature, consider filing an issue to find out if support for your scenario can be added.

We don't aim to replace Newtonsoft.JSON. If it works for you, continue to use it. We'll do our best to make System.Text.Json as useful as possible while maintaining the gains we've achieved in performance, security, standards compliance, and layering. Over time we hope to make it work for as many folks as possible. I am hearing the interest here and will make sure we focus on this moving forward.

Thanks for your suggestions. I've finally gone by the middle, using Newtonsoft serializer only for deserialization, but I still have to test. I hope it doesn't last too much to get ExpandoObject support on the default serializer of CosmosDB so I could get rid of the old one.
@RobbyDeLaet, thanks for your comment! I don't have much expectatives about Microsoft implementing anyting, I've felt fustrated too much times about them. As a freelance developer I've waited for years, literally, for essential features to be implemented on CosmosDB, EntityFramework and Identity infrastructure, and they make their own way ignoring us. It doesn't matter how many votes they have on the UserVoice or whatever. I supose that such features aren't needed for it's own developments so are out of scope...
The problem is always the same: Too much marketing = too much expectatives

To help those that need this functionality, see the code samples above to help unblock:

  • From @tchivs (which uses ExpandoObject for objects and List<object> for collections)
  • From @denmitchell (which uses IL Emit for objects instead of ExpandoObject)

To help with requirements for this feature I will provide a new custom converter sample and will link that here when it's ready. After that I'll get that sample added to the Newtonsoft work-around section. cc @tdykstra

One detail not yet discussed is that supporting dynamic requires a reference to the very large System.Linq.Expressions.dll assembly; this complication may mean in the future we add the dynamic converter in a new assembly (e.g. System.Text.Json.Converters.dll) to get pay-to-play for those that care about deployment size (such as a self-contained app or a Blazor client app).

Just for my own sanity - is the issue scoped for 5.0 specifically ExpandoObject support or the ability to deserialize into a dynamic object? The conversation here doesn't all seem to match the issue title, and I'm definitely needing the latter. In my case I'm deserializing a nested dynamic, specifically List<Dictionary<string, dynamic>>. Switched back to Newtonsoft for now :(

I am now testing an old function I implemented to deserialize nested objects and seeing the same issue noted in this topic with "valueKind".

Was there a way to fix this using the new neetonsoft.json packages? Because the above code worked for the root level object but not for the nested objects.

@ericstj Apologies, my reaction was perhaps a bit too negative and harsh, but like @SocVi100 I'm a freelance developer and sometimes frustrations get the upper hand. But at least we got a discussion started here. Thank you for your response, now we got at least a clear message about the status of this request.

Just for my own sanity - is the issue scoped for 5.0 specifically ExpandoObject support or the ability to deserialize into a dynamic object

I assume the desired semantics are to have a dynamic type so after deserializing you can access all properties (including nested) in a late-bound manner:

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

and I assume the desired semantics also support ExpandoObject explicitly:

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

So my understanding of the scope:

  • Deserialize<dynamic>(json) returns either a primitive, collection or object. An object here will likely be ExpandoObject but could be IDynamicMetaObjectProvider or a JITed type depending on implementation. This is different than today which always returns JsonElement.
  • Deserialize<ExpandoObject>(json) (which implements IDynamicMetaObjectProvider) should create a proper ExpandoObject which in turn can be used with dynamic. This is different than today which only creates expando-properties for the root properties and JsonElement instances for all nested properties.
  • Serialize<ExpandoObect>() works as expected
  • Serialize<IDynamicMetaObjectProvider()> may or may not be implemented depending on whether there are scenarios where ExpandoObject is not used.

Semantics in 3.0 - 5.0:

  • Serializing an ExpandoObject works since ExpandoObject implements IDictionary<string, object> and STJ will correctly serialize each object value whether it is a primitive, collection or object.
  • Deserializing into an ExpandoObject sort of works but only the root properties are "proper" expando-properties; any nested properties will be JsonElement so there is inconsistency in the programming model thus deserializing ExpandoObject should be avoided unless a custom converter is used for that.

Options for 3.0-5.0:

  • Write a custom converter for ExpandoObject and\or object as mentioned in the posts above; I'll provide a link soon to a new sample that ideally maps to a 6.0 feature.
  • If you only serialize, not deserialize, you can use ExpandoObject (or dynamic if the type is based on ExpandoObject). Since deserializing ExpandoObject has an inconsistency issue today in STJ, ExpandoObject is not recommended if deserializing.
  • Use JsonElement instead of dynamic objects. JsonElement doesn't attempt to "guess" what type the JSON maps to, so you have to be explicit by calling GetString(), GetInt32(), etc.

Also, depending on implementation, the implementation of a custom converter likely means there is some "guessing" about what CLR types the JSON maps to during deserialization. For example, a JSON string may map to either a DateTime or a string, a JSON number may map to a double or a long, and the JSON array type needs to be determined. This needs to be vetted and compared against Newtonsoft semantics. This should also align to what type is returned when a property is of Type object (today it is JsonElement).

Here's some examples of 3.0 - 5.0 STJ semantics:

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 Can you fix your code snippet based on what @ryan-hollister-q2 pointed out?

As promised, the PR providing a dynamic implementation sample is at https://github.com/dotnet/runtime/pull/42097.

@rs38 Can you fix your code snippet based on what @ryan-hollister-q2 pointed out?

that wasn't my code, just shared some snippets from @tchivs earlier in this thread.

Was this page helpful?
0 / 5 - 0 ratings