Code/Core/NetworkStorageExtensions.cs

Extension helpers for parsing JsonElement responses from network storage. Provides convenience methods to read ints, floats, strings, bools, lists, dictionaries, and to compare local state against server data.

Networking
using System;
using System.Collections.Generic;
using System.Text.Json;

namespace Sandbox;

/// <summary>
/// Extension helpers for working with Network Storage JSON responses.
/// Reduces boilerplate when parsing endpoint responses into game objects.
///
/// Usage:
///   var ores = response.ReadDictionary( "ores", prop => (float)prop.GetDouble() );
///   var upgrades = response.ReadStringList( "purchasedUpgrades" );
///   var level = response.Int( "level", 1 );
/// </summary>
public static class NetworkStorageExtensions
{
	/// <summary>Get an int from a JsonElement, with fallback.</summary>
	public static int Int( this JsonElement el, string key, int fallback = 0 )
		=> JsonHelpers.GetInt( el, key, fallback );

	/// <summary>Get a float from a JsonElement, with fallback.</summary>
	public static float Float( this JsonElement el, string key, float fallback = 0f )
		=> JsonHelpers.GetFloat( el, key, fallback );

	/// <summary>Get a string from a JsonElement, with fallback.</summary>
	public static string Str( this JsonElement el, string key, string fallback = "" )
		=> JsonHelpers.GetString( el, key, fallback );

	/// <summary>Get a bool from a JsonElement, with fallback.</summary>
	public static bool Bool( this JsonElement el, string key, bool fallback = false )
		=> JsonHelpers.GetBool( el, key, fallback );

	/// <summary>
	/// Read a JSON array property as a List of strings.
	/// Returns empty list if property missing or wrong type.
	/// </summary>
	public static List<string> ReadStringList( this JsonElement el, string key )
	{
		var list = new List<string>();
		if ( !el.TryGetProperty( key, out var arr ) || arr.ValueKind != JsonValueKind.Array )
			return list;

		foreach ( var item in arr.EnumerateArray() )
		{
			if ( item.ValueKind == JsonValueKind.String )
				list.Add( item.GetString() );
		}
		return list;
	}

	/// <summary>
	/// Read a JSON object property as a Dictionary with a value converter.
	/// Useful for ore inventories: response.ReadDictionary("ores", v => (float)v.GetDouble())
	/// </summary>
	public static Dictionary<string, T> ReadDictionary<T>( this JsonElement el, string key, Func<JsonElement, T> converter )
	{
		var dict = new Dictionary<string, T>();
		if ( !el.TryGetProperty( key, out var obj ) || obj.ValueKind != JsonValueKind.Object )
			return dict;

		foreach ( var prop in obj.EnumerateObject() )
		{
			try { dict[prop.Name] = converter( prop.Value ); }
			catch { /* skip malformed entries */ }
		}
		return dict;
	}

	/// <summary>
	/// Read a JSON array property as a List with a row converter.
	/// Useful for tables: response.ReadList("rows", row => new OreInfo(row))
	/// </summary>
	public static List<T> ReadList<T>( this JsonElement el, string key, Func<JsonElement, T> converter )
	{
		var list = new List<T>();
		if ( !el.TryGetProperty( key, out var arr ) || arr.ValueKind != JsonValueKind.Array )
			return list;

		foreach ( var item in arr.EnumerateArray() )
		{
			try { list.Add( converter( item ) ); }
			catch { /* skip malformed entries */ }
		}
		return list;
	}

	/// <summary>
	/// Compare local state against a server response and return a list of mismatches.
	/// Fields is a dictionary of fieldName → (localValue, serverExtractor).
	/// </summary>
	public static List<string> FindMismatches( this JsonElement serverData, Dictionary<string, (object Local, Func<JsonElement, object> Extract)> fields )
	{
		var mismatches = new List<string>();
		foreach ( var (name, (local, extract)) in fields )
		{
			try
			{
				var remote = extract( serverData );
				if ( !Equals( local, remote ) )
					mismatches.Add( $"{name}: local={local} server={remote}" );
			}
			catch { /* field not present */ }
		}
		return mismatches;
	}
}