using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework.Input; using Newtonsoft.Json; namespace StardewModdingAPI.Framework.Serialisation { /// Encapsulates SMAPI's JSON file parsing. internal class JsonHelper { /********* ** Accessors *********/ /// The JSON settings to use when serialising and deserialising files. private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded Converters = new List { new SelectiveStringEnumConverter(), new SelectiveStringEnumConverter(), new SelectiveStringEnumConverter() } }; /********* ** Public methods *********/ /// Read a JSON file. /// The model type. /// The absolete file path. /// Returns the deserialised model, or null if the file doesn't exist or is empty. /// The given path is empty or invalid. public TModel ReadJsonFile(string fullPath) where TModel : class { // validate if (string.IsNullOrWhiteSpace(fullPath)) throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); // read file string json; try { json = File.ReadAllText(fullPath); } catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) { return null; } // deserialise model try { return JsonConvert.DeserializeObject(json, this.JsonSettings); } catch (JsonReaderException ex) { string message = $"The file at {fullPath} doesn't seem to be valid JSON."; string text = File.ReadAllText(fullPath); if (text.Contains("“") || text.Contains("”")) message += " Found curly quotes in the text; note that only straight quotes are allowed in JSON."; message += $"\nTechnical details: {ex.Message}"; throw new JsonReaderException(message); } } /// Save to a JSON file. /// The model type. /// The absolete file path. /// The model to save. /// The given path is empty or invalid. public void WriteJsonFile(string fullPath, TModel model) where TModel : class { // validate if (string.IsNullOrWhiteSpace(fullPath)) throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); // create directory if needed string dir = Path.GetDirectoryName(fullPath); if (dir == null) throw new ArgumentException("The file path is invalid.", nameof(fullPath)); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); // write file string json = JsonConvert.SerializeObject(model, this.JsonSettings); File.WriteAllText(fullPath, json); } } }