Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
235 views
in Technique[技术] by (71.8m points)

c# - Getting nested properties with System.Text.Json

I am working with System.Text.Json in my project as I am processing large files so also decided to use it for processing GraphQL responses.

Due to the nature of GraphQL sometimes I get highly nested responses that are not fixed and don't make sense to map to a class. I usually need to check a few properties on the response.

My issue is with JsonElement. To check nested properties feels very clumsy and I feel like there should be a better way to approach this.

For example take my below code simulating a response I get. I just want to check if 2 properties exist (id & originalSrc) and if they do get their value but it feels like I have made a meal of the code. Is there a better/clearer/more succinct way to write this?

var 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""
                }
            }
        }
    ]
}
}
}";

var doc = JsonSerializer.Deserialize<JsonElement>(raw);

JsonElement node = new JsonElement();

string productIdString = null;

if (doc.TryGetProperty("data", out var data))
    if (data.TryGetProperty("products", out var products))
        if (products.TryGetProperty("edges", out var edges))
            if (edges.EnumerateArray().FirstOrDefault().ValueKind != JsonValueKind.Undefined && edges.EnumerateArray().First().TryGetProperty("node", out node))
                if (node.TryGetProperty("id", out var productId))
                    productIdString = productId.GetString();

string originalSrcString = null;

if(node.ValueKind != JsonValueKind.Undefined && node.TryGetProperty("featuredImage", out var featuredImage))
    if (featuredImage.TryGetProperty("originalSrc", out var originalSrc))
        originalSrcString = originalSrc.GetString();

if (!string.IsNullOrEmpty(productIdString))
{
    //do stuff
}

if (!string.IsNullOrEmpty(originalSrcString))
{
    //do stuff
}

It is not a crazy amount of code but checking a handful of properties is so common I would like a cleaner more readble approach.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You could add a couple of extension methods that access a child JsonElement value by property name or array index, returning a nullable value if not found:

public static partial class JsonExtensions
{
    public static JsonElement? Get(this JsonElement element, string name) => 
        element.ValueKind != JsonValueKind.Null && element.ValueKind != JsonValueKind.Undefined && element.TryGetProperty(name, out var value) 
            ? value : (JsonElement?)null;

    public static JsonElement? Get(this JsonElement element, int index)
    {
        if (element.ValueKind == JsonValueKind.Null || element.ValueKind == JsonValueKind.Undefined)
            return null;
        var value = element.EnumerateArray().ElementAtOrDefault(index);
        return value.ValueKind != JsonValueKind.Undefined ? value : (JsonElement?)null;
    }
}

Now calls to access nested values can be chained together using the null-conditional operator ?.:

var doc = JsonSerializer.Deserialize<JsonElement>(raw);

var node = doc.Get("data")?.Get("products")?.Get("edges")?.Get(0)?.Get("node");

var productIdString = node?.Get("id")?.GetString();
var originalSrcString = node?.Get("featuredImage")?.Get("originalSrc")?.GetString();
Int64? someIntegerValue = node?.Get("Size")?.GetInt64();  // You could use "var" here also, I used Int64? to make the inferred type explicit.

Notes:

  • The extension methods above will throw an exception if the incoming element is not of the expected type (object or array or null/missing). You could loosen the checks on ValueKind if you never want an exception on an unexpected value type.

  • There is an open API enhancement request Add JsonPath support to JsonDocument/JsonElement #31068. Querying via JSONPath, if implemented, would make this sort of thing easier.

Demo fiddle here.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...