Create dedicated JSON API
Pathoschild opened this issue ยท 5 comments
SMAPI has two sets of methods to read/write JSON files on the mod helper:
/****
** Mod config file
****/
/// <summary>Read the mod's configuration file (and create it if needed).</summary>
/// <typeparam name="TConfig">The config class type. This should be a plain class that has public properties for the settings you want. These can be complex types.</typeparam>
TConfig ReadConfig<TConfig>() where TConfig : class, new();
/// <summary>Save to the mod's configuration file.</summary>
/// <typeparam name="TConfig">The config class type.</typeparam>
/// <param name="config">The config settings to save.</param>
void WriteConfig<TConfig>(TConfig config) where TConfig : class, new();
/****
** Generic JSON files
****/
/// <summary>Read a JSON file.</summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="path">The file path relative to the mod directory.</param>
/// <returns>Returns the deserialised model, or <c>null</c> if the file doesn't exist or is empty.</returns>
TModel ReadJsonFile<TModel>(string path) where TModel : class;
/// <summary>Save to a JSON file.</summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="path">The file path relative to the mod directory.</param>
/// <param name="model">The model to save.</param>
void WriteJsonFile<TModel>(string path, TModel model) where TModel : class;
This API has a few disadvantages:
- These are the only methods on the mod helper itself; everything else is on a nested object.
- The semantics are hardcoded. For example, the
config.json
is always auto-created on read but custom JSON files are never auto-created. - The distinction between config and custom files is often confusing (e.g. a popular question is "How do I read custom files using
ReadConfig
?"). - There's no way to read/write a string, which would be useful for some mods (e.g. to parsing an HTTP response).
Consider creating a dedicated JSON API.
Possible JSON API:
/// <summary>An API for reading and writing JSON data.</summary>
public interface IJsonHelper
{
/*********
** Public methods
*********/
/// <summary>Read data from a JSON file.</summary>
/// <typeparam name="TConfig">The config class type. This should be a plain class that has public properties for the settings you want.</typeparam>
/// <param name="path">The file path relative to the mod folder.</param>
/// <param name="autocreate">Whether to create the file automatically if it doesn't exist.</param>
/// <returns>Returns the deserialised model if it exists; else the default model if <paramref name="autocreate"/> is <c>true</c>; else <c>null</c>.</returns>
TConfig ReadFile<TConfig>(string path = "config.json", bool autocreate = true) where TConfig : class, new();
/// <summary>Save data to a JSON file.</summary>
/// <typeparam name="TConfig">The config class type. This should be a plain class that has public properties for the settings you want.</typeparam>
/// <param name="data">The data to save to the file.</param>
/// <param name="path">The file json relative to the mod folder.</param>
void WriteFile<TConfig>(TConfig data, string path = "config.json") where TConfig : class, new();
/// <summary>Read a JSON string.</summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="json">The JSON string to parse.</param>
/// <returns>Returns the deserialised model.</returns>
TModel ReadString<TModel>(string json);
/// <summary>Get a JSON string.</summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="model">The model to serialise.</param>
string WriteString<TModel>(TModel model);
}
Benefits of this API:
- There's one method each to read a file/write a file, whether it's
config.json
or a custom file. - You can enable autocreation with custom files, or disable it for
config.json
. - Added support for read/writing JSON strings.
Example usage:
- Read the conventional
config.json
:var config = helper.Json.ReadFile<ModConfig>();
- Read a custom JSON file:
var model = helper.Json.ReadFile<ModData>("data.json");
- Read a custom JSON string (e.g. for parsing an HTTP response):
var model = helper.Json.ReadString<ModData>(json);
To consider: should SMAPI encapsulate per-save data? For example, the mod could have this:
helper.Json.WritePerSaveFile(model, "data.json");
And SMAPI would automatically save it to <mod folder>/data/<save ID>/data.json
.
I like the basic idea, and think that combining ReadJson/ReadConfig & WriteJson/WriteConfig into a single Write & Read set will work, but I feel that anyone who is going to be reading JSON from a string... is already doing complex enough things that directly referencing newtonsoft json might be the smarter idea...
As to per-save jsons, I can agree to the functionality, but not the location - any per-save data should be stored as part of the save, storing it in the Mods folder is simply wrong.
Where per-save JSON should be stored is controversial based on our Discord chat, so we can look into adding that later. :)
Closed in favor of the unified data-storage API in #468.