// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
namespace EpicGames.Core
{
///
/// Exception thrown for errors parsing JSON files
///
public class JsonParseException : Exception
{
///
/// Constructor
///
/// Format string
/// Optional arguments
public JsonParseException(string format, params object[] args)
: base(String.Format(format, args))
{
}
}
///
/// Stores a JSON object in memory
///
public class JsonObject
{
readonly Dictionary _rawObject;
///
/// Construct a JSON object from the raw string -> object dictionary
///
/// Raw object parsed from disk
public JsonObject(Dictionary inRawObject)
{
_rawObject = new Dictionary(inRawObject, StringComparer.InvariantCultureIgnoreCase);
}
///
/// Constructor
///
///
public JsonObject(JsonElement element)
{
_rawObject = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
foreach (JsonProperty property in element.EnumerateObject())
{
_rawObject[property.Name] = ParseElement(property.Value);
}
}
///
/// Parse an individual element
///
///
///
public static object? ParseElement(JsonElement element)
{
switch(element.ValueKind)
{
case JsonValueKind.Array:
return element.EnumerateArray().Select(x => ParseElement(x)).ToArray();
case JsonValueKind.Number:
return element.GetDouble();
case JsonValueKind.Object:
return element.EnumerateObject().ToDictionary(x => x.Name, x => ParseElement(x.Value));
case JsonValueKind.String:
return element.GetString();
case JsonValueKind.False:
return false;
case JsonValueKind.True:
return true;
case JsonValueKind.Null:
return null;
default:
throw new NotImplementedException();
}
}
///
/// Read a JSON file from disk and construct a JsonObject from it
///
/// File to read from
/// New JsonObject instance
public static JsonObject Read(FileReference file)
{
string text = FileReference.ReadAllText(file);
try
{
return Parse(text);
}
catch(Exception ex)
{
throw new JsonParseException("Unable to parse {0}: {1}", file, ex.Message);
}
}
///
/// Tries to read a JSON file from disk
///
/// File to read from
/// On success, receives the parsed object
/// True if the file was read, false otherwise
public static bool TryRead(FileReference fileName, [NotNullWhen(true)] out JsonObject? result)
{
if (!FileReference.Exists(fileName))
{
result = null;
return false;
}
string text = FileReference.ReadAllText(fileName);
return TryParse(text, out result);
}
///
/// Parse a JsonObject from the given raw text string
///
/// The text to parse
/// New JsonObject instance
public static JsonObject Parse(string text)
{
JsonDocument document = JsonDocument.Parse(text, new JsonDocumentOptions { AllowTrailingCommas = true });
return new JsonObject(document.RootElement);
}
///
/// Try to parse a JsonObject from the given raw text string
///
/// The text to parse
/// On success, receives the new JsonObject
/// True if the object was parsed
public static bool TryParse(string text, [NotNullWhen(true)] out JsonObject? result)
{
try
{
result = Parse(text);
return true;
}
catch (Exception)
{
result = null;
return false;
}
}
///
/// List of key names in this object
///
public IEnumerable KeyNames => _rawObject.Keys;
///
/// Gets a string field by the given name from the object, throwing an exception if it is not there or cannot be parsed.
///
/// Name of the field to get
/// The field value
public string GetStringField(string fieldName)
{
string? stringValue;
if (!TryGetStringField(fieldName, out stringValue))
{
throw new JsonParseException("Missing or invalid '{0}' field", fieldName);
}
return stringValue;
}
///
/// Tries to read a string field by the given name from the object
///
/// Name of the field to get
/// On success, receives the field value
/// True if the field could be read, false otherwise
public bool TryGetStringField(string fieldName, [NotNullWhen(true)] out string? result)
{
object? rawValue;
if (_rawObject.TryGetValue(fieldName, out rawValue) && (rawValue is string strValue))
{
result = strValue;
return true;
}
else
{
result = null;
return false;
}
}
///
/// Gets a string array field by the given name from the object, throwing an exception if it is not there or cannot be parsed.
///
/// Name of the field to get
/// The field value
public string[] GetStringArrayField(string fieldName)
{
string[]? stringValues;
if (!TryGetStringArrayField(fieldName, out stringValues))
{
throw new JsonParseException("Missing or invalid '{0}' field", fieldName);
}
return stringValues;
}
///
/// Tries to read a string array field by the given name from the object
///
/// Name of the field to get
/// On success, receives the field value
/// True if the field could be read, false otherwise
public bool TryGetStringArrayField(string fieldName, [NotNullWhen(true)] out string[]? result)
{
object? rawValue;
if (_rawObject.TryGetValue(fieldName, out rawValue) && (rawValue is IEnumerable