[Fork] https://stackoverflow.com/questions/61553962/getting-nested-properties-with-system-text-json

CSharp · Console

/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters;
using System.IO;
using System.Data;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using System.Collections.ObjectModel;

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

using NUnit.Framework; // Throws NUnit.Framework.AssertionException

//https://stackoverflow.com/questions/61553962/getting-nested-properties-with-system-text-json

	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;
			// Throw if index < 0
			return index < element.GetArrayLength() ? element[index] : null;
		}
	}
		
    class TestClass
    {
        public static void Test()
        {
			TestArray();
			
			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""
							}
						}
					}
				]
			}
			}
			}";	
			Console.WriteLine("Testing JSON with keys present:");
			TestGood(raw);
			Test(raw);
			
			Console.WriteLine("\nTesting JSON with keys missing:");
			var bad1 = @"{
			""data"": {
			""products"": {
				""XXedges"": [
					{
						""node"": {
							""id"": ""gid://shopify/Product/4534543543316"",
							""featuredImage"": {
								""originalSrc"": ""https://cdn.shopify.com/s/files/1/0286/pic.jpg"",
								""id"": ""gid://shopify/ProductImage/146345345339732""
							}
						}
					}
				]
			}
			}
			}";	
			Test(bad1);
			
			var bad2 = @"{
			""data"": {
			""products"": {
				""edges"": []
			}
			}
			}";	

			Test(bad2);
			
			var bad3 = @"{
			""data"": {
			""products"": {
				""edges"": null
			}
			}
			}";	
			Test(bad3);
			
			var bad4 = @"{
			""data"": {
			""products"": {
				""edges"": [null]
			}
			}
			}";	
			Test(bad4);
		}
		
		static void TestGood(string raw)
		{
			var doc = JsonSerializer.Deserialize<JsonElement>(raw);

			Assert.Throws<KeyNotFoundException>(() => doc.GetProperty("does-not-exist"));
			
			var node = doc.Get("data")?.Get("products")?.Get("edges")?[0].Get("node");
			Assert.IsNotNull(node);
			Assert.AreEqual(null, doc.Get("data")?.Get("XXXXproducts")?.Get("edges")?[0].Get("node"));
			Assert.AreEqual(null, doc.Get("data")?.Get("products")?.Get("XXXedges")?[0].Get("node"));
			Assert.AreEqual(null, doc.Get("data")?.Get("products")?.Get("edges")?[0].Get("XXXnode"));
		}
		static void Test(string raw)
		{
			var doc = JsonSerializer.Deserialize<JsonElement>(raw);

			Assert.Throws<KeyNotFoundException>(() => doc.GetProperty("does-not-exist"));
			
			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.

			Console.WriteLine("productIdString={0}, originalSrcString={1}, someIntegerValue={2}", productIdString, originalSrcString, someIntegerValue); 
		}	
		
		static void TestArray()
		{
			var arrayJson = "[0]";
			
			var arrayDoc = JsonSerializer.Deserialize<JsonElement>(arrayJson);
			
			Assert.AreEqual(0, arrayDoc.Get(0).Value.GetInt32());
			Assert.IsNull(arrayDoc.Get(1));
			Assert.Throws<IndexOutOfRangeException>(() => arrayDoc.Get(-1));
		}
	}

	public static partial class JsonExtensions
	{
		public static JsonElement? Get(this JsonDocument doc, string name) => doc.RootElement.Get(name);
		public static JsonElement? Get(this JsonDocument doc, int index) => doc.RootElement.Get(index);
	}

	public class Program
	{
		public static void Main()
		{
			Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
			Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name,  typeof(JsonSerializer).Assembly.FullName);
			Console.WriteLine();

			try
			{
				TestClass.Test();
			}
			catch (Exception ex)
			{
				Console.WriteLine("Failed with unhandled exception: ");
				Console.WriteLine(ex);
				throw;
			}
		}
		
		public static string GetNetCoreVersion()
		{
			//https://techblog.dorogin.com/how-to-detect-net-core-version-in-runtime-ecd65ad695be
			//
			var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
			var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
			int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
			if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
				return assemblyPath[netCoreAppIndex + 1];
			return null;
		}
	}
;