SMAPI - Stardew Modding API

SMAPI - Stardew Modding API

971k Downloads

Create dedicated JSON API

Pathoschild opened this issue ยท 5 comments

commented

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.

commented

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);
commented

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.

commented

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.

commented

Where per-save JSON should be stored is controversial based on our Discord chat, so we can look into adding that later. :)

commented

Closed in favor of the unified data-storage API in #468.