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

10

74

I have this json

{"id":"48e86841-f62c-42c9-ae20-b54ba8c35d6d"}

How do I get the 48e86841-f62c-42c9-ae20-b54ba8c35d6d out of it? All examples I can find show to do something like

var o = System.Text.Json.JsonSerializer.Deserialize<some-type>(json);
o.id // <- here's the ID!

But I don't have a type that fits this definition and I don't want to create one. I've tried deserializing to dynamic but I was unable to get that working.

var result = System.Text.Json.JsonSerializer.Deserialize<dynamic>(json);
result.id // <-- An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in System.Linq.Expressions.dll but was not handled in user code: ''System.Text.Json.JsonElement' does not contain a definition for 'id''

Can anyone give any suggestions?


edit:

I just figured out I can do this:

Guid id = System.Text.Json.JsonDocument.Parse(json).RootElement.GetProperty("id").GetGuid();

This does work - but is there a better way?

Serpentiform answered 28/8, 2019 at 22:14 Comment(3)
Can you use a 3rd party lib like Newtonsoft's Json?Pigeonhole
No, we've abandoned newtsonsoft because of how slow it is.Serpentiform
I don't consider what I added to be an answer.Serpentiform
U
58

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.

Uranography answered 28/8, 2019 at 22:24 Comment(7)
Dictionary<string, object> ain't a bad option too.Chronometer
How do you read value from dynamic object ? because I am getting an error RuntimeBinderException: 'System.Text.Json.JsonElement' does not contain a definition for 'MyProperty'Roanne
Unfortunately, Dictionary<string, string> will fail if the response Json contains string and non string types i.e. numbers.Botsford
"Or just deserialize to Object which will yield a JsonElement that you can call GetProperty on." Is there an example you can point me to?Charter
using System.Text.Json; var json = JsonSerializer.Deserialize<object>(json) as JsonElement?; var tmp = json?.GetProperty("id")Earthstar
I think you can as well do this: var je_root = JsonSerializer.Deserialize<JasonElement>(jsonstr); and then access it e.g. like so: int myvalue = je_root.GetProperty("MyProperty").GetProperty("MySubProperty").GetInt32();Fiddling
@Roanne - if you need to test if a property exists, you can deserialise to a JsonElement and then use TryGetProperty: JsonSerializer.Deserialize<JsonElement>().TryGetProperty("SomeProp", out var element)Cinema
P
31

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

Plagiary answered 10/11, 2021 at 13:34 Comment(2)
@Spathic Now I understand and then you are correct. Added working example code with dynamicPlagiary
Thanks - I think that's better. I got very confused yesterday when I couldn't get the dynamic syntax working. Hopefully it'll prevent someone else being confused too! I'll tidy up my commentsSpathic
D
17

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.

Davita answered 3/11, 2019 at 11:59 Comment(1)
JsonDocument needs to be disposed when it goes out of scope, according to the docs, to prevent a memory leak. When you need to use the RootElement outside the lifetime of the document, you must clone it.Oleander
B
11

Actual way to parse string in System.Text.Json (.NET Core 3+)

        var jsonStr = "{\"id\":\"48e86841-f62c-42c9-ae20-b54ba8c35d6d\"}";
        using var doc = JsonDocument.Parse(jsonStr);
        var root = doc.RootElement;
        var id = root.GetProperty("id").GetGuid();
Bean answered 1/12, 2020 at 10:30 Comment(0)
I
4

I wrote an extension method for this purpose. You can safely use as following:

var jsonElement = JsonSerializer.Deserialize<JsonElement>(json);
var guid = jsonElement.TryGetValue<Guid>("id");

This is the extension class.

public static class JsonElementExtensions
{
    private static readonly JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true };

    public static T? TryGetValue<T>(this JsonElement element, string propertyName)
    {
        if (element.ValueKind != JsonValueKind.Object)
        {
            return default;
        }

        element.TryGetProperty(propertyName, out JsonElement property);

        if (property.ValueKind == JsonValueKind.Undefined ||
            property.ValueKind == JsonValueKind.Null)
        {
            return default;
        }

        try
        {
            return property.Deserialize<T>(options);
        }
        catch (JsonException)
        {
            return default;
        }
    }
}

Reason

The reason behind using this extension instead of JsonNode class is because if you need a Controller method accepts just an object without exposing it's model class Asp.Net Core model binding uses JsonElement struct to map the json string. At this point (as far as I know) there is no simple way to convert the JsonElement to JsonNode and when your object can be anything the JsonElement methods will throw exceptions for undefined fields while JsonNode don't.

[HttpPost]
public IActionResult Post(object setupObject)
{
    var setup = (JsonElement)setupObject;
    var id = setup.TryGetValue<Guid>("id");
    var user = setup.TryGetValue<User?>("user");
    var account = setup.TryGetValue<Account?>("account");
    var payments = setup.TryGetValue<IEnumerable<Payments>?>("payments");

    // ...

    return Ok();
}
Indeciduous answered 21/6, 2022 at 15:48 Comment(0)
S
1

You can use the following extension method to query data like "xpath"

public static string? JsonQueryXPath(this string value, string xpath, JsonSerializerOptions? options = null) => value.Deserialize<JsonElement>(options).GetJsonElement(xpath).GetJsonElementValue();

        
        public static JsonElement GetJsonElement(this JsonElement jsonElement, string xpath)
        {
            if (jsonElement.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
                return default;

            string[] segments = xpath.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries);

            foreach (var segment in segments)
            {
                if (int.TryParse(segment, out var index) && jsonElement.ValueKind == JsonValueKind.Array)
                {
                    jsonElement = jsonElement.EnumerateArray().ElementAtOrDefault(index);
                    if (jsonElement.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
                        return default;

                    continue;
                }

                jsonElement = jsonElement.TryGetProperty(segment, out var value) ? value : default;

                if (jsonElement.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
                    return default;
            }

            return jsonElement;
        }

        public static string? GetJsonElementValue(this JsonElement jsonElement) => jsonElement.ValueKind != JsonValueKind.Null &&
                                                                                   jsonElement.ValueKind != JsonValueKind.Undefined
            ? jsonElement.ToString()
            : default;

Simple to use as follows

string raw = @"{
        ""data"": {
        ""products"": {
            ""edges"": [
                {
                    ""node"": {
                        ""id"": ""gid://shopify/Product/4534543543316"",
                        ""featuredImage"": {
                            ""originalSrc"": ""https://cdn.shopify.com/s/files/1/0286/pic.jpg"",
                            ""id"": ""gid://shopify/ProductImage/146345345339732""
                        }
                    }
                },
                {
                    ""node"": {
                        ""id"": ""gid://shopify/Product/123456789"",
                        ""featuredImage"": {
                            ""originalSrc"": ""https://cdn.shopify.com/s/files/1/0286/pic.jpg"",
                            ""id"": [
                                ""gid://shopify/ProductImage/123456789"",
                                ""gid://shopify/ProductImage/666666666""
                            ]
                        },
                        ""1"": {
                            ""name"": ""Tuanh""
                        }
                    }
                }
            ]
        }
        }
    }";

            System.Console.WriteLine(raw2.QueryJsonXPath("data.products.edges.0.node.featuredImage.id"));
Sandrocottus answered 19/6, 2021 at 12:49 Comment(0)
S
0

Solution that worked for me in .NET 6 Azure HTTPTrigger Function, without using class object

    [Function("HTTPTrigger1")]
    public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
    {
        // Input: { "name": "Azure", "id": 123 }
        var reqBody = new StreamReader(req.Body).ReadToEnd();
        JsonObject obj = JsonNode.Parse(reqBody).AsObject();
        obj.TryGetPropertyValue ("name", out JsonNode jsnode);
        string name2 = jsnode.GetValue<string>();
        obj.TryGetPropertyValue ("id", out jsnode);
        int id2 = jsnode.GetValue<int>();

        // OR
        // using dictionary
        var data = JsonSerializer.Deserialize<Dictionary<string, object>> (reqBody);
        string name = data["name"].ToString () ?? "Anonymous";
        int.TryParse(data["id"].ToString(), out int id);

        _logger.LogInformation($"Hi {name}{id} {name2}{id2}. C# HTTP trigger function processed a request.");
        var response = req.CreateResponse(HttpStatusCode.OK);
        response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
        response.WriteString("Welcome to Azure Functions!");
        return response;
    }

This is what I would use for quick testing purposes rather than clean POCO object.

Somnambulate answered 5/6, 2023 at 14:6 Comment(0)
B
-1

update to .NET Core 3.1 to support

public static dynamic FromJson(this string json, JsonSerializerOptions options = null)
    {
        if (string.IsNullOrEmpty(json))
            return null;

        try
        {
            return JsonSerializer.Deserialize<ExpandoObject>(json, options);
        }
        catch
        {
            return null;
        }
    }
Backward answered 7/12, 2019 at 8:31 Comment(3)
JsonSerializer.Deserialize<ExpandoObject> doesn't help you to extract value from JsonValueKind and upgrading .Net Core 3.1 doesn't change the behaviour of System.Text.JsonDoucette
@Doucette You are not right, you can easily cast ExpandoObject to JsonElement and get everythingDinsmore
It works for the first level properties but for sublevels doesn't work. It cast to {System.Text.Json.JsonElement}Electrodynamics
O
-1

You can also deserialize your json to an object of your target class, and then read its properties as per normal:

var obj = DeSerializeFromStrToObj<ClassToSerialize>(jsonStr);
Console.WriteLine($"Property: {obj.Property}");

where DeSerializeFromStrToObj is a custom class that makes use of reflection to instantiate an object of a targeted class:

    public static T DeSerializeFromStrToObj<T>(string json)
    {
        try
        {
            var o = (T)Activator.CreateInstance(typeof(T));

            try
            {
                var jsonDict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);

                var props = o.GetType().GetProperties();

                if (props == null || props.Length == 0)
                {
                    Debug.WriteLine($"Error: properties from target class '{typeof(T)}' could not be read using reflection");
                    return default;
                }

                if (jsonDict.Count != props.Length)
                {
                    Debug.WriteLine($"Error: number of json lines ({jsonDict.Count}) should be the same as number of properties ({props.Length})of our class '{typeof(T)}'");
                    return default;
                }

                foreach (var prop in props)
                {
                    if (prop == null)
                    {
                        Debug.WriteLine($"Error: there was a prop='null' in our target class '{typeof(T)}'");
                        return default;
                    }

                    if (!jsonDict.ContainsKey(prop.Name))
                    {
                        Debug.WriteLine($"Error: jsonStr does not refer to target class '{typeof(T)}'");
                        return default;
                    }

                    var value = jsonDict[prop.Name];
                    Type t = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
                    object safeValue = value ?? Convert.ChangeType(value, t);
                    prop.SetValue(o, safeValue, null); // initialize property
                }
                return o;
            }
            catch (Exception e2)
            {
                Debug.WriteLine(e2.Message);
                return o;
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
            return default;
        }
    }

You can test your jsons for example here

Here you find a complete working example with different ways of serialization and deserialization that might be of interest for you and/or future readers:

using System;
using System.Collections.Generic;
using System.Text.Json;
using static Json_Tests.JsonHelpers;

namespace Json_Tests
{

public class Class1
{
    public void Test()
    {
        var obj1 = new ClassToSerialize();
        var jsonStr = obj1.ToString();

        // if you have the class structure for the jsonStr (for example, if you have created the jsonStr yourself from your code):
        var obj2 = DeSerializeFromStrToObj<ClassToSerialize>(jsonStr);
        Console.WriteLine($"{nameof(obj2.Name)}: {obj2.Name}");

        // if you do not have the class structure for the jsonStr (forexample, jsonStr comes from a 3rd party service like the web):
        var obj3 = JsonSerializer.Deserialize<object>(jsonStr) as JsonElement?;
        var propName = nameof(obj1.Name);
        var propVal1 = obj3?.GetProperty("Name");// error prone
        Console.WriteLine($"{propName}: {propVal1}");
        JsonElement propVal2 = default;
        obj3?.TryGetProperty("Name", out propVal2);// error prone
        Console.WriteLine($"{propName}: {propVal2}");

        var obj4 = DeSerializeFromStrToDict(jsonStr);
        foreach (var pair in obj4)
            Console.WriteLine($"{pair.Key}: {pair.Value}");
    }
}

[Serializable]
public class ClassToSerialize
{
    // important: properties must have at least getters
    public string Name { get; } = "Paul";
    public string Surname{ get; set; } = "Efford";

    public override string ToString() => JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
}

public static class JsonHelpers
{
    /// <summary>
    /// to use if you do not have the class structure for the jsonStr (forexample, jsonStr comes from a 3rd party service like the web)
    /// </summary>
    public static Dictionary<string, string> DeSerializeFromStrToDict(string json)
    {
        try
        {
            return JsonSerializer.Deserialize<Dictionary<string, string>>(json);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            return new Dictionary<string, string>(); // return empty
        }
    }

    /// <summary>
    /// to use if you have the class structure for the jsonStr (for example, if you have created the jsonStr yourself from your code)
    /// </summary>
    public static T DeSerializeFromStrToObj<T>(string json) // see this: https://json2csharp.com/#
    {
        try
        {
            var o = (T)Activator.CreateInstance(typeof(T));

            try
            {
                var jsonDict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);

                var props = o.GetType().GetProperties();

                if (props == null || props.Length == 0)
                {
                    Console.WriteLine($"Error: properties from target class '{typeof(T)}' could not be read using reflection");
                    return default;
                }

                if (jsonDict.Count != props.Length)
                {
                    Console.WriteLine($"Error: number of json lines ({jsonDict.Count}) should be the same as number of properties ({props.Length})of our class '{typeof(T)}'");
                    return default;
                }

                foreach (var prop in props)
                {
                    if (prop == null)
                    {
                        Console.WriteLine($"Error: there was a prop='null' in our target class '{typeof(T)}'");
                        return default;
                    }

                    if (!jsonDict.ContainsKey(prop.Name))
                    {
                        Console.WriteLine($"Error: jsonStr does not refer to target class '{typeof(T)}'");
                        return default;
                    }

                    var value = jsonDict[prop.Name];
                    Type t = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
                    object safeValue = value ?? Convert.ChangeType(value, t);
                    prop.SetValue(o, safeValue, null); // initialize property
                }
                return o;
            }
            catch (Exception e2)
            {
                Console.WriteLine(e2.Message);
                return o;
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            return default;
        }
    }
}
}
Outstay answered 23/12, 2021 at 14:22 Comment(0)
E
-1

simply do this:

var obj = System.Text.Json.JsonSerializer.Deserialize<JsonNode>(jsonString);
var id = obj["id"]; // <- here's the ID!
Ethelstan answered 6/7 at 9:8 Comment(3)
This answer is not useful, because you are repeating this answer: https://mcmap.net/q/270103/-how-do-you-read-a-simple-value-out-of-some-json-using-system-text-jsonAsha
You do not need to use Dictionary<string, string>, JsonNode will simply do the job. Moreover, Dictionary<string, string> can be wrong, at least you have to use Dictionary<string, object> ;-). The question asks for a SIMPLE solution and mine is simpler.Ethelstan
The info about "Dictionary<string, string> can be wrong," should have been added to the answer, to make it a better answerAsha

© 2022 - 2024 — McMap. All rights reserved.