How do you read a simple value out of some json using System.Text.Json?

I've recently migrated a project from ASP.NET Core 2.2 to 3, and I'm having this inconvenience. In our team we value lean dependencies, so we are trying to avoid including Newtonsoft.JSON back and try using System.Text.Json. We also decided not to use a ton of POCO objects just for JSON serialization, because our backend models are more complex than needed for Web APIs. Also, because of nontrivial behaviour encapsulation, the backend models cannot be easily used to serialize/deserialize JSON strings.

I understand that System.Text.Json is supposed to be faster than Newtonsoft.JSON, but I believe this has a lot to do with ser/deser from/to specific POCO classes. Anyway, speed was not on our list of pros/cons for this decision, so YMMV.

Long story short, for the time being I wrote a small dynamic object wrapper that unpacks the JsonElements from System.Text.Json and tries to convert/cast as best as possible. The typical usage is to read the request body as a dynamic object. Again, I'm pretty sure this approach kills any speed gains, but that was not a concern for our use case.

This is the class:

    public class ReflectionDynamicObject : DynamicObject {
        public JsonElement RealObject { get; set; }

        public override bool TryGetMember (GetMemberBinder binder, out object result) {
            // Get the property value
            var srcData = RealObject.GetProperty (binder.Name);

            result = null;

            switch (srcData.ValueKind) {
                case JsonValueKind.Null:
                    result = null;
                    break;
                case JsonValueKind.Number:
                    result = srcData.GetDouble ();
                    break;
                case JsonValueKind.False:
                    result = false;
                    break;
                case JsonValueKind.True:
                    result = true;
                    break;
                case JsonValueKind.Undefined:
                    result = null;
                    break;
                case JsonValueKind.String:
                    result = srcData.GetString ();
                    break;
                case JsonValueKind.Object:
                    result = new ReflectionDynamicObject {
                        RealObject = srcData
                    };
                    break;
                case JsonValueKind.Array:
                    result = srcData.EnumerateArray ()
                        .Select (o => new ReflectionDynamicObject { RealObject = o })
                        .ToArray ();
                    break;
            }

            // Always return true; other exceptions may have already been thrown if needed
            return true;
        }
    }

and this is an example usage, to parse the request body - one part is in a base class for all my WebAPI controllers, that exposes the body as a dynamic object:

    [ApiController]
    public class WebControllerBase : Controller {

        // Other stuff - omitted

        protected async Task<dynamic> JsonBody () {
            var result = await JsonDocument.ParseAsync (Request.Body);
            return new ReflectionDynamicObject {
                RealObject = result.RootElement
            };
        }
    }

and can be used in the actual controller like this:

//[...]
    [HttpPost ("")]
    public async Task<ActionResult> Post () {
        var body = await JsonBody ();
        var name = (string) body.Name;
        //[...]
    }
//[...]

If needed, you can integrate parsing for GUIDs or other specific data types as needed - while we all wait for some official / framework-sanctioned solution.


you can deserialize to a Dictionary:

var dict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(json)

Or just deserialize to Object which will yield a JsonElement that you can call GetProperty on.


Support for JsonObject has been added in .NET 6 using System.Text.Json.Nodes.

Example:

const string Json = "{\"MyNumber\":42, \"MyArray\":[10,11]}";
// dynamic
{
    dynamic obj = JsonNode.Parse(Json);
    int number = (int)obj["MyNumber"];
    Debug.Assert(number == 42);

    obj["MyString"] = "Hello";
    Debug.Assert((string)obj["MyString"] == "Hello");
}

// JsonObject 
{
    JsonObject obj = JsonNode.Parse(Json).AsObject();
    int number = (int)obj["MyNumber"];
    Debug.Assert(number == 42);
    
    obj["MyString"] = "Hello";
    Debug.Assert((string)obj["MyString"] == "Hello");
}

Sources:

https://github.com/dotnet/runtime/issues/53195

https://github.com/dotnet/runtime/issues/45188