for Push.
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// JSONファイルのピンポイント1項目更新
|
||||
/// </summary>
|
||||
/// <param name="path">JSONファイルパス</param>
|
||||
/// <param name="jsonPath">JSONノードパス</param>
|
||||
/// <param name="newValue">更新値</param>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSONファイルから、jsonPathに一致する単一項目を取得します。
|
||||
/// 取得に成功したら true を返し、 out value に JToken を設定します。
|
||||
/// キーが見つからない・JSON無効・ファイル無し等は false を返し、 value は null。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文字列として取得(JToken型を意識したくない場合の軽量ヘルパー)。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 符号付き整数(Int64)で取得(intの範囲超えも想定)。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任意型の既定値付き取得(存在しなければ defaultValue を返す)。
|
||||
/// 例:GetJsonKeyOrDefault(path, "app.auth.retries", 3)
|
||||
/// </summary>
|
||||
public static T GetJsonKeyOrDefault<T>(string path, string jsonPath, T defaultValue)
|
||||
{
|
||||
if (!TryGetJsonKey(path, jsonPath, out var token) || token is null)
|
||||
return defaultValue;
|
||||
|
||||
try
|
||||
{
|
||||
// JToken → 任意型に変換(失敗時は既定値)
|
||||
var val = token.ToObject<T>();
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user