Files
HLib/IO/File/JsonFileUxer.cs
2026-06-01 13:23:34 +09:00

207 lines
7.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
}
}