Skip to content

Instantly share code, notes, and snippets.

@saulm314
Last active November 15, 2025 06:03
Show Gist options
  • Select an option

  • Save saulm314/91f0d83ce1a931b5086169e17b6e4eb0 to your computer and use it in GitHub Desktop.

Select an option

Save saulm314/91f0d83ce1a931b5086169e17b6e4eb0 to your computer and use it in GitHub Desktop.
NewtonSoft.Json Utils
// MIT License: https://gist.github.com/saulm314/91f0d83ce1a931b5086169e17b6e4eb0
using System;
public abstract class JsonContentException(string? message) : Exception(message)
{
public class Empty() : JsonContentException("base JSON object { } not found");
public class PropertyNotFound(string path, string propertyName)
: JsonContentException($"JSON property {path.Dereference(propertyName)} not found".AddPathWarning())
{
public string Path => path;
public string PropertyName => propertyName;
}
public class InvalidConversion<T>(string path, object? value) : InvalidConversion(typeof(T), path, value);
public class InvalidConversion(Type desiredType, string path, object? value)
: JsonContentException($"""
Could not convert value:
{value}
in JSON path:
{path}
to type:
{desiredType.FullName}
""".AddPathWarning())
{
public Type DesiredType => desiredType;
public string Path => Path;
public object? Value => value;
}
public class InvalidValueAssignment<T>(string path) : InvalidValueAssignment(typeof(T), path);
public class InvalidValueAssignment(Type type, string path)
: JsonContentException($"Cannot assign a value of type {type.FullName} to JValue at {path}".AddPathWarning())
{
public Type PType => type;
public string Path => path;
}
public class OutOfRange(string path, int index)
: JsonContentException($"Index {index} out of range of JArray at {path}".AddPathWarning())
{
public string Path => path;
public int Index => index;
}
}
// MIT License: https://gist.github.com/saulm314/91f0d83ce1a931b5086169e17b6e4eb0
// Wrap: https://gist.github.com/saulm314/bf4c6cd9e4a9b045b52ad123a80a5c39
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static class JsonUtils
{
public static JObject Deserialize(string jsonString) => (JObject)(JsonConvert.DeserializeObject(jsonString) ?? throw new JsonContentException.Empty());
public static T As<T>(this JToken jtoken) where T : notnull
{
if (typeof(T).IsAssignableTo(typeof(JToken)))
{
if (jtoken is not T _jtoken)
throw new JsonContentException.InvalidConversion<T>(jtoken.Path, jtoken);
return _jtoken;
}
if (jtoken is not JValue jvalue)
throw new JsonContentException.InvalidConversion<T>(jtoken.Path, jtoken);
if (jvalue.Value is not T value)
throw new JsonContentException.InvalidConversion<T>(jvalue.Path, jvalue.Value);
return value;
}
public static Wrap<T>? AsN<T>(this JToken jtoken) where T : notnull
{
if (typeof(T).IsAssignableTo(typeof(JToken)))
{
if (jtoken is not T _jtoken)
return null;
return _jtoken;
}
if (jtoken is not JValue jvalue)
return null;
if (jvalue.Value is not T value)
return null;
return value;
}
public static bool Is<T>(this JToken jtoken) where T : notnull
{
if (typeof(T).IsAssignableTo(typeof(JToken)))
return jtoken is T;
if (jtoken is not JValue jvalue)
return false;
return jvalue.Value is T;
}
public static bool IsNull(this JToken jtoken)
{
if (jtoken is not JValue jvalue)
return false;
return jvalue.Value == null;
}
public static JToken D(this JToken jtoken, string propertyName)
=> jtoken.As<JObject>()[propertyName] ?? throw new JsonContentException.PropertyNotFound(jtoken.Path, propertyName);
public static JToken? DN(this JToken jtoken, string propertyName) => (jtoken as JObject)?[propertyName];
public static T D<T>(this JToken jtoken, string propertyName) where T : notnull => jtoken.D(propertyName).As<T>();
public static Wrap<T>? DN<T>(this JToken jtoken, string propertyName) where T : notnull
{
JToken? subtoken = jtoken.DN(propertyName);
if (subtoken == null)
return null;
return subtoken.AsN<T>();
}
public static bool ContainsProperty(this JToken jtoken, string propertyName) => (jtoken as JObject)?.ContainsKey(propertyName) ?? false;
public static bool ContainsProperty<T>(this JToken jtoken, string propertyName) where T : notnull => jtoken.DN(propertyName)?.Is<T>() ?? false;
public static bool ContainsProperty(this JToken jtoken, string propertyName, out JToken? subtoken)
{
subtoken = jtoken.DN(propertyName);
return subtoken != null;
}
public static bool ContainsProperty<T>(this JToken jtoken, string propertyName, out Wrap<T>? subtoken) where T : notnull
{
subtoken = jtoken.DN<T>(propertyName);
return subtoken != null;
}
public static JToken At(this JToken jtoken, int index)
{
JArray jarray = jtoken.As<JArray>();
if (index < 0 || index >= jarray.Count)
throw new JsonContentException.OutOfRange(jarray.Path, index);
return jarray[index];
}
public static JToken? AtN(this JToken jtoken, int index)
{
if (jtoken is not JArray jarray)
return null;
if (index < 0 || index >= jarray.Count)
return null;
return jarray[index];
}
public static T At<T>(this JToken jtoken, int index) where T : notnull => jtoken.At(index).As<T>();
public static Wrap<T>? AtN<T>(this JToken jtoken, int index) where T : notnull
{
JToken? subtoken = jtoken.AtN(index);
if (subtoken == null)
return null;
return subtoken.AsN<T>();
}
public static void SetNull(this JToken jtoken) => jtoken.As<JValue>().Value = null;
public static bool TrySetNull(this JToken jtoken)
{
if (jtoken is not JValue jvalue)
return false;
jvalue.Value = null;
return true;
}
public static void Set<T>(this JToken jtoken, T value)
{
try
{
jtoken.As<JValue>().Value = value;
}
catch (ArgumentException)
{
throw new JsonContentException.InvalidValueAssignment<T>(jtoken.Path);
}
}
public static bool TrySet<T>(this JToken jtoken, T value)
{
if (jtoken is not JValue jvalue)
return false;
try
{
jvalue.Value = value;
return true;
}
catch (ArgumentException)
{
return false;
}
}
public static void SetNull(this JToken jtoken, string propertyName) => jtoken.As<JObject>()[propertyName] = JValue.CreateNull();
public static bool TrySetNull(this JToken jtoken, string propertyName)
{
if (jtoken is not JObject jobject)
return false;
jobject[propertyName] = JValue.CreateNull();
return true;
}
public static void Set<T>(this JToken jtoken, string propertyName, T value) => jtoken.As<JObject>()[propertyName] = ToJToken(value);
public static bool TrySet<T>(this JToken jtoken, string propertyName, T value)
{
if (jtoken is not JObject jobject)
return false;
jobject[propertyName] = ToJToken(value);
return true;
}
public static void SetNull(this JToken jtoken, int index)
{
JArray jarray = jtoken.As<JArray>();
if (index < 0 || index >= jarray.Count)
throw new JsonContentException.OutOfRange(jarray.Path, index);
jarray[index] = JValue.CreateNull();
}
public static bool TrySetNull(this JToken jtoken, int index)
{
if (jtoken is not JArray jarray)
return false;
if (index < 0 || index >= jarray.Count)
return false;
jarray[index] = JValue.CreateNull();
return true;
}
public static void Set<T>(this JToken jtoken, int index, T value)
{
JArray jarray = jtoken.As<JArray>();
if (index < 0 || index >= jarray.Count)
throw new JsonContentException.OutOfRange(jarray.Path, index);
jarray[index] = ToJToken(value);
}
public static bool TrySet<T>(this JToken jtoken, int index, T value)
{
if (jtoken is not JArray jarray)
return false;
if (index < 0 || index >= jarray.Count)
return false;
jarray[index] = ToJToken(value);
return true;
}
public static T DeepClone<T>(this JToken jtoken) where T : notnull => jtoken.DeepClone().As<T>();
public static Wrap<T>? DeepCloneN<T>(this JToken jtoken) where T : notnull => jtoken.DeepClone().AsN<T>();
public static JToken ToJToken(object? obj)
{
if (obj == null)
return JValue.CreateNull();
if (obj is JToken jtoken)
return jtoken;
try
{
return new JValue(obj);
}
catch (ArgumentException)
{
return JToken.FromObject(obj);
}
}
public static string AddPathWarning(this string message)
=> $"{message} (path may not be the original path if JSON object was modified at the time of the exception being thrown)";
public static string Dereference(this string path, string propertyName)
=> string.IsNullOrWhiteSpace(path) ? propertyName : path + '.' + propertyName;
}
MIT License
Copyright (c) 2025 Saulius Markevicius
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Newtonsoft.Json Utils

When using the Newtonsoft.Json library, there are several problems with navigating through a JToken, especially with clunkiness and error handling.

Example

Example JSON Snippet

{
    "someObject": {
        "someInnerObject": {
            "innerInnerValue": 5
        }
    }
}

Native Newtonsoft.Json Handling

public static long GetInnerInnerValue(string jsonStr)
{
    JObject jobject = JsonConvert.DeserializeObject<JObject>(jsonStr)!;
    JValue innerInnerJvalue = (JValue)jobject["someObject"]!["someInnerObject"]!["innerInnerValue"]!;
    long innerInnerValue = (long)innerInnerJvalue.Value!;
    return innerInnerValue;
}

public static long? GetInnerInnerValueOrNull(string jsonStr)
{
    JObject? jobject = JsonConvert.DeserializeObject<JObject>(jsonStr);
    JValue? innerInnerJvalue = (JValue?)jobject?["someObject"]?["someInnerObject"]?["innerInnerValue"];
    long? innerInnerValue = innerInnerJvalue?.Value as long?;
    return innerInnerValue;
}

public static long GetInnerInnerValueOrThrow(string jsonStr)
{
    JObject jobject = JsonConvert.DeserializeObject<JObject>(jsonStr) ?? throw new JsonContentException("Could not parse JSON string");
    JToken someObjectJtoken = jobject["someObject"] ?? throw new JsonContentException("someObject not found in JSON");
    JObject someObject = someObjectJtoken as JObject ?? throw new JsonContentException("someObject is not a valid JSON object");
    JToken someInnerObjectJtoken = someObject["someInnerObject"] ?? throw new JsonContentException("someInnerObject not found in JSON");
    JObject someInnerObject = someInnerObjectJtoken as JObject ?? throw new JsonContentException("someInnerObject is not a valid JSON object");
    JToken innerInnerValueJtoken = someInnerObject["innerInnerValue"] ?? throw new JsonContentException("innerInnerValue not found in JSON");
    JValue innerInnerValueJvalue = innerInnerValueJtoken as JValue ?? throw new JsonContentException("innerInnerValue does not have a valid JSON value");
    long innerInnerValue = innerInnerValueJvalue.Value as long? ?? throw new JsonContentException("innerInnerValue is not a valid integer");
    return innerInnerValue;
}

public class JsonContentException(string? message) : Exception(message);

JsonUtils Handling

// automatically throws the appropriate JsonContentException and explains where the problem is
// JsonUtils.Deserialize tries to deserialize or throws a JsonContentException if the string is empty
//   (Newtonsoft.Json already throws JsonException if the JSON itself is invalid)
// the D method stands for Dereference -
//   it dereferences a JSON object or throws a JsonContentException explaining why it can't do that
// the As<T> method converts a JToken to type T (supports values, strings, JToken subtypes, etc.)
//   or throws a JsonContentException explaining why it can't do that
public static long GetInnerInnerValue(string jsonStr)
{
    JObject jobject = JsonUtils.Deserialize(jsonStr);
    long innerInnerValue = jobject.D("someObject").D("someInnerObject").D("innerInnerValue").As<long>();
    return innerInnerValue;
}

// when a method name has N appended to it, it does the same thing but returns null instead of throwing an error
// so DN dereferences or returns null, AsN<T> converts to type T or returns null
public static long? GetInnerInnerValueOrNull(string jsonStr)
{
    JObject? jobject = JsonConvert.DeserializeObject<JObject>(jsonStr);
    long? innerInnerValue = jobject?.DN("someObject")?.DN("someInnerObject")?.DN("innerInnerValue")?.AsN<long>()?.Value;
    return innerInnerValue;
}

// GetInnerInnerValue already handles exceptions, so no need for special implementation
public static long GetInnerInnerValueOrThrow(string jsonStr) => GetInnerInnerValue(jsonStr);

Methods

// deserialize or throw JsonException if JSON is invalid, or JsonContentException if string is empty
JObject Deserialize(string jsonString);

// convert a JToken to type T or throw JsonContentException
T As<T>(this JToken jtoken) where T : notnull;

// convert a JToken to type T or return null
Wrap<T>? AsN<T>(this JToken jtoken) where T : notnull;

// return true if jtoken is of type T, or if it is a JValue and its value is of type T, false otherwise
bool Is<T>(this JToken jtoken) where T : notnull;

// return true if jtoken is a JValue and its value is null, false otherwise
bool IsNull(this JToken jtoken);

// dereference the jtoken and return a new JToken, or throw JsonContentException
JToken D(this JToken jtoken, string propertyName);

// dereference the jtoken and return a new JToken, or return null
JToken? DN(this JToken jtoken, string propertyName);

// dereference the jtoken and convert it to type T, or throw JsonContentException
T D<T>(this JToken jtoken, string propertyName) where T : notnull;

// dereference the jtoken and convert it to type T, or return null
Wrap<T>? DN<T>(this JToken jtoken, string propertyName) where T : notnull;

// return true if jtoken is a JObject and contains a property with name propertyName, false otherwise
bool ContainsProperty(this JToken jtoken, string propertyName);

// return true if jtoken is a JObject and contains a property with name propertyName and is of type T,
//   where T is either a JToken (or subtype) or a JSON value such as string/long; false otherwise
bool ContainsProperty<T>(this JToken jtoken, string propertyName) where T : notnull;

// return true if jtoken is a JObject and contains a property with name propertyName and gives the property as a subtoken;
//   otherwise returns false and gives a null subtoken
bool ContainsProperty(this JToken jtoken, string propertyName, out JToken? subtoken);

// return true if jtoken is a JObject and contains a property with name propertyName and is of type T,
//   and gives the property as a subtoken; otherwise returns false and gives a null subtoken
bool ContainsProperty<T>(this JToken jtoken, string propertyName, out Wrap<T>? subtoken) where T : notnull;

// return the JToken with the specified index of a JArray, or throw JsonContentException if jtoken is not an array or if out of range
JToken At(this JToken jtoken, int index);

// return the JToken with the specified index of a JArray, or return null
JToken? AtN(this JToken jtoken, int index);

// return the value of type T with the specified index of a JArray, or throw JsonContentException if jtoken is not an array,
//  or if out of range
T At<T>(this JToken jtoken, int index) where T : notnull;

// return the value of type T with the specified index of a JArray, or return null
Wrap<T>? AtN<T>(this JToken jtoken, int index) where T : notnull;

// set the value of jtoken to null, or throw JsonContentException if jtoken is not a JValue
void SetNull(this JToken jtoken);

// set the value of jtoken to null and return true, or return false if jtoken is not a JValue
bool TrySetNull(this JToken jtoken);

// set the value of jtoken to value, or throw JsonContentException if jtoken is not a JValue or if T is invalid
// T can only be a direct JSON value like string/long
void Set<T>(this JToken jtoken, T value);

// set the value of jtoken to value and return true, or return false if jtoken is not a JValue or if T is invalid
// T can only be a direct JSON vlaue like string/long
bool TrySet<T>(this JToken jtoken, T value);

// set the given property of jtoken to null or throw JsonContentException
// NOTE: this is different from SetNull(this JToken jtoken) in that if this property already existed,
//   it replaces the entire jtoken with a JValue (with a value of null)
//   in particular, this method can be called even if the property was not previously a JValue
void SetNull(this JToken jtoken, string propertyName);

// set the given property of jtoken to null and return true, or return false if not possible
// NOTE: if the property already existed, this replaces the entire jtoken with a JValue (with a value of null)
bool TrySetNull(this JToken jtoken, string propertyName);

// set the given property of jtoken to value or throw JsonContentException
// NOTE: if the property already existed, this replaces the entire jtoken with a new one
// T can either be JToken (or a subtype), or a direct JSON value like string/long
void Set<T>(this JToken jtoken, string propertyName, T value);

// set the given property of jtoken to value and return true, or return false if not possible
// NOTE: if the property already existed, this replaces the entire jtoken with a new one
// T can either be JToken (or a subtype), or a direct JSON value like string/long
bool TrySet<T>(this JToken jtoken, string propertyName, T value);

// set the given element of a JArray to null or throw JsonContentException
// NOTE: this replaces the entire jtoken at that element with a JValue (with a value of null)
void SetNull(this JToken jtoken, int index);

// set the given element of a JArray to null and return true, or return false if not possible
// NOTE: this replaces the entire jtoken at that element with a JValue (with a value of null)
bool TrySetNull(this JToken jtoken, int index);

// set the given element of a JArray to value or throw JsonContentException
// NOTE: this replaces the entire jtoken with a new one
// T can either be JToken (or a subtype), or a direct JSON value like string/long
void Set<T>(this JToken jtoken, int index, T value);

// set the given element of a JArray to value and return true, or return false if not possible
// NOTE: this replaces the entire jtoken with a new one
// T can either be JToken (or a subtype), or a direct JSON value like string/long
bool TrySet<T>(this JToken jtoken, int index, T value);

// deep clone the jtoken and convert the result to type T or throw JsonContentException
T DeepClone<T>(this JToken jtoken) where T : notnull;

// deep clone the jtoken and convert the result to type T or return null if conversion is invalid
Wrap<T>? DeepCloneN<T>(this JToken jtoken) where T : notnull;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment