using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace KssSmaPlaLib.IO.File { public static class JsonFileUxer { /// /// JSONファイルのピンポイント1項目更新 /// /// JSONファイルパス /// JSONノードパス /// 更新値 /// public static void UpdateJsonKeyAtomic(string path, string jsonPath, object newValue) { //// 使い方例:パスワード更新 //JsonFiileUxer.UpdateJsonKeyAtomic("config.json", "app.auth.password", "new_pw_2026-01"); var jtoken = JToken.FromObject(newValue); // 1) 読み込み(UTF-8) var json = System.IO.File.ReadAllText(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); var jo = JObject.Parse(json); // 2) ピンポイント更新(途中が無ければ作る) var token = jo.SelectToken(jsonPath); if (token is null) { // 例: "app.auth.password" を分解して動的に作る var parts = jsonPath.Split('.'); JToken cur = jo; for (int i = 0; i < parts.Length; i++) { var name = parts[i]; var obj = cur as JObject ?? throw new InvalidOperationException("親がオブジェクトではありません"); if (i == parts.Length - 1) { obj[name] = jtoken; } else { if (obj[name] == null) obj[name] = new JObject(); cur = obj[name]; } } } else { token.Replace(jtoken); } // 3) 一時ファイルへ書き出し var dir = System.IO.Path.GetDirectoryName(path) ?? "."; var tmp = System.IO.Path.Combine(dir, ".tmp_config.json"); var backup = path + $"_{DateTime.Now.ToString("yyyyMMddHHmmss")}" + ".bak"; //var output = jo.ToString(Formatting.Indented) + Environment.NewLine; var output = JsonConvert.SerializeObject(jo, Formatting.Indented) + Environment.NewLine; System.IO.File.WriteAllText(tmp, output, new UTF8Encoding(false)); // 4) バックアップ作成 → 原子置換 if (System.IO.File.Exists(path)) System.IO.File.Copy(path, backup, overwrite: true); System.IO.File.Replace(tmp, path, backup); } /// /// JSONファイルから、jsonPathに一致する単一項目を取得します。 /// 取得に成功したら true を返し、 out value に JToken を設定します。 /// キーが見つからない・JSON無効・ファイル無し等は false を返し、 value は null。 /// public static bool TryGetJsonKey(string path, string jsonPath, out JToken value) { value = null; // 1) 読み込み(UTF-8 BOMなし) string json; try { json = System.IO.File.ReadAllText(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } catch (Exception) { // ファイルがない/読み取り不可 return false; } // 2) 解析 JObject jo; try { jo = JObject.Parse(json); } catch (JsonReaderException) { // JSON不正 return false; } // 3) ピンポイント取得 // 例: "app.auth.password" / "items[0].name" / "users['alice'].age" 等 try { var token = jo.SelectToken(jsonPath); if (token is null) { return false; // キー未存在 } value = token; // JValue/JObject/JArray いずれもOK return true; } catch (ArgumentException) { // JSONPathの書式誤り(SelectTokenが例外) return false; } } /// /// 文字列として取得(JToken型を意識したくない場合の軽量ヘルパー)。 /// public static bool TryGetJsonString(string path, string jsonPath, out string result) { result = null; if (!TryGetJsonKey(path, jsonPath, out var token)) return false; // JValue以外(オブジェクト/配列)の場合は ToString() を許容するか判断(ここではJValueのみ厳密) if (token is JValue jv) { // 文字列ならそのまま if (jv.Type == JTokenType.String) { result = (string)jv.Value; return true; } // 数値・真偽・Null などは ToString() で返す設計でもOK // ここでは文字列のみ厳密に true とします(お好みで分岐調整) return false; } return false; } /// /// 符号付き整数(Int64)で取得(intの範囲超えも想定)。 /// public static bool TryGetJsonInt64(string path, string jsonPath, out long result) { result = default; if (!TryGetJsonKey(path, jsonPath, out var token)) return false; if (token is JValue jv) { // JValueが数値型なら long へ if (jv.Type == JTokenType.Integer) { // Newtonsoftは内部的に long を持つのでキャスト可能 try { result = Convert.ToInt64(jv.Value); return true; } catch { return false; } } // 文字列数値を許可する場合("42" → 42) if (jv.Type == JTokenType.String && long.TryParse((string)jv.Value, out var parsed)) { result = parsed; return true; } } return false; } /// /// 任意型の既定値付き取得(存在しなければ defaultValue を返す)。 /// 例:GetJsonKeyOrDefault(path, "app.auth.retries", 3) /// public static T GetJsonKeyOrDefault(string path, string jsonPath, T defaultValue) { if (!TryGetJsonKey(path, jsonPath, out var token) || token is null) return defaultValue; try { // JToken → 任意型に変換(失敗時は既定値) var val = token.ToObject(); return val == null ? defaultValue : val; } catch { return defaultValue; } } } }