Skip to content

Simplify how mods use config.json #159

@Pathoschild

Description

@Pathoschild

Consider simplifying how mods deal with config.json.

Current system

Here's how mods use config.json now:

  1. The mod creates a subclass of StardewModdingAPI.Config with an overridden method:

    internal class SampleConfig : StardewModdingAPI.Config
    {
        public bool ExampleBoolean { get; set; }
        public float ExampleFloat { get; set; }
    
        public override T GenerateDefaultConfig<T>()
        {
            this.ExampleBoolean = true;
            this.ExampleFloat = 0.5;
            return this as T;
        }
    }
  2. The mod loads it by calling a method on the subclass with the mod's config path:

    var config = new SampleConfig().InitializeConfig(this.BaseConfigPath);

If the mod wants to use a separate JSON file (e.g. Lookup Anything's data.json):

  1. Reference the Newtonsoft.Json package. (Make sure to use the same version as SMAPI to avoid issues.)

  2. Read the file:

    string path = Path.Combine(this.PathOnDisk, "data.json");
    string data = File.ReadAllText(path);
    var customModel = JsonConvert.DeserialiseObject<Model>(data);
  3. Optionally resave the file if you want to use SMAPI's auto-create logic:

    string data = JsonConvert.SerialiseObject(customModel);
    File.WriteAllText(path, data);

Pros & cons of current system

  • Advantages:

    • ✓ Existing mods already use it.
    • ✓ It's well-tested, since it's been in use for seven months now.
  • Disadvantages:

    • ✘ It ignores C# conventions.
      This makes it harder for new mod developers. For example, it's easy to forget to override Config.GenerateDefaultConfig<T>, which will break. It's also not clear how it should be used — should you set the defaults in the constructor and return the instance, or set the defaults in that method and return the instance, or return a new object? Why not use a constructor?
    • ✘ The interface is confusing and exposes SMAPI internals.
      For example, what's the difference between Config.InitializeConfig<T>(), Config.LoadConfig<T>(), Config.ReloadConfig<T>(), and Config.Instance<T>()? How about between Config.UpdateConfig<T>() and Config.WriteConfig<T>()?
    • ✘ It adds a lot of boilerplate.
      Ideally the mod author shouldn't need to care how the config system works (e.g. implementing an internal base class or explicitly calling an initialiser).
    • ✘ If the mod wants to use custom JSON files, it needs to reference the underlying library and do it manually.
    • ✘ The internal implementation is a bit complicated, since it reinvents some things that C# and the JSON library do out of the box (like merging defaults).

Proposed system

Here's one idea for a new config system:

  1. The mod creates a plain class and sets defaults in the usual C# way:

    internal class SampleConfig
    {
        public bool ExampleBoolean { get; set; } = true;
        public float ExampleFloat { get; set; } = 0.5;
    }

    (A constructor would work too.)

  2. The mod loads it using a mod method:

    var config = this.Helper.ReadConfig<SampleConfig>();

If the mod wants to use a separate JSON file (e.g. Lookup Anything's data.json):

  1. The mod loads it the same way:

    var model = this.Helper.ReadJson<CustomModel>("data.json");

Pros & cons of proposed system

  • Advantages:

    • ✓ It uses C# conventions.
      This reduces the learning curve, since mod developers can apply their existing knowledge. They already know how to set default values in C#; why reinvent the wheel?
    • ✓ The interface is discoverable.
      The mod's base class only has two properties: this.Manifest and this.Helper. It's easy to discover what features are available without needing to look up separate documentation. The SMAPI internals are hidden away to avoid confusion.
    • ✓ It eliminates boilerplate.
      The mod author just creates a simple class — no base class, overridden methods, or passing paths into a certain method.
    • ✓ If the mod wants to use custom JSON files, that's easy to do.
    • ✓ The ModHelper is easy to extend with more methods in the future.
    • ✓ It's easy to implement internally, since that's how the Json.NET library is meant to be used.
  • Disadvantages:

    • ✘ For backwards compatibility with existing mods, SMAPI would need to support the old system too (and mark it deprecated).

Metadata

Metadata

Assignees

Labels

enhancementThis is a general improvement that can be addressed with specific development changes.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions