dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.98k stars 4.66k forks source link

Support "dynamic" and writable DOM #29690

Closed justinushermawan closed 3 years ago

justinushermawan commented 5 years ago

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

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

ahsonkhan commented 5 years ago

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.
deinok commented 5 years ago

@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

tiksn commented 5 years ago

@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.

SidShetye commented 5 years ago

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.

MikeReznikov commented 5 years ago

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?

SidShetye commented 5 years ago

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

ahsonkhan commented 4 years ago

@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

estiller commented 4 years ago

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.

ahsonkhan commented 4 years ago

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);
sheryever commented 4 years ago

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

daviddesmet commented 4 years ago

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.

lfr commented 4 years ago

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

ExpandoObject's been in the BCL for a looooong time

fatihyildizhan commented 4 years ago

Is there any alternative for ExpandoObject?

sheryever commented 4 years ago

@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

corbinmunce commented 4 years ago

I was surprised this isn't supported yet.

ahsonkhan commented 4 years ago

Moving this to 5.0.

RobbyDeLaet commented 4 years ago

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

Laiteux commented 4 years ago

5.0? wow, that definitely sucks.

AlexeiScherbakov commented 4 years ago

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);
        }
    }
Arash-Sabet commented 4 years ago

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

tchivs commented 4 years ago

.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:

    /// <summary>
    /// Temp Dynamic Converter
    /// by:tchivs@live.cn
    /// </summary>
    public class DynamicJsonConverter : JsonConverter<dynamic>
    {
        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?

var serializerOptions = new JsonSerializerOptions
{
    Converters = { new DynamicJsonConverter() }
};
return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);
denmitchell commented 4 years ago

@tchivs, your solution worked for me; but since the Converters property is read-only, I had to do something like this:

var serializerOptions = new JsonSerializerOptions();
serializerOptions.Converters.Add(new DynamicJsonConverter());
return JsonSerializer.Deserialize<dynamic>("{OK:"200"}", serializerOptions);
zehavibarak commented 4 years ago

Try using the JsonElement type:

public JsonElement MyProperty {get; set;}

denmitchell commented 4 years ago

@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.

GuerrillaCoder commented 4 years ago

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.

mauroservienti commented 4 years ago

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?

ahsonkhan commented 4 years ago

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).

Lanayx commented 4 years ago

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.

zsalkus commented 4 years ago

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 :)

k3davis commented 4 years ago

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 :(

rs38 commented 4 years ago

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

/// <summary>
/// Temp Dynamic Converter with c# 8
/// by:tchivs@live.cn
/// </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());
        }
}
ryan-hollister-q2 commented 4 years ago

@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,

xavierlaffargue commented 4 years ago

@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!

SocVi100 commented 4 years ago

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.

RobbyDeLaet commented 4 years ago

@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.

rs38 commented 4 years ago

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)

RobbyDeLaet commented 4 years ago

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

I wish I could.

tjrobinson commented 4 years ago

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

ericstj commented 4 years ago

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.

SocVi100 commented 4 years ago

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

steveharter commented 4 years ago

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

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).

Julius-ERCM commented 4 years ago

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.

RobbyDeLaet commented 4 years ago

@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.

steveharter commented 4 years ago

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:

Semantics in 3.0 - 5.0:

Options for 3.0-5.0:

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);
            }
        }
    }
}
TAGC commented 4 years ago

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

steveharter commented 4 years ago

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

rs38 commented 4 years ago

@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.

John0King commented 3 years ago

I saw many people in this issue talk about the dynamic but what about the Writable JSON DOM ?
As a Newtonsoft.Json user, I won't use dynamic, instead I use JToken , because in many case the json is data: T | T[] , and to handle those data, you can't use just dynamic because it hard to know what is the type of the object.
the JToken solve 99% of the serialize/deserialize problem , and we can "corrent" the JToken to right form (or add necessary other properties ), and then send to other place (eg. return to client or write to db or some message handler)

steveharter commented 3 years ago

I saw many people in this issue talk about the dynamic but what about the Writable JSON DOM ?

The proposal for 6.0 is to support for dynamic and non-dynamic cases with an overlapping but consistent programming model. Newtsoft does this as well since JToken implements IDynamicMetaObjectProvider which supports dynamic. This means if you don't want to use dynamic, you don't have to. In general, I would expect most consumers to not use dynamic since it is slower.

and we can "corrent" the JToken to right form

Can you elaborate on "correcting" to make sure we have the scenario. I assume you mean changing the DOM to modify a value of a property\element or by adding\removing properties\elements.

John0King commented 3 years ago

@steveharter

Can you elaborate on "correcting" to make sure we have the scenario. I assume you mean changing the DOM to modify a value of a property\element or by adding\removing properties\elements.

yes, for example change the data:T|T[] to data:T[] . I can't do it (or hard) with dynamic because I need to know the type of the property/object , and JToken can do this perfectly because every property/object have a TokenType property.

but today the JsonDocument JsonElement is just a StringReader or SpanReader (compare to newtonsoft.Json is new JsonTextReader(new StringReader(ref jsonString)) , just that today we can't pass by ref) ,
and it seems that this issue became to use dynamic to serialize and deserialize the object again and again, it will be way slower than JToken like "C# object" , and hard to detect it's type .

we don't need a string reader we need the "C# object" !