原文链接
2023年5月24日
However,我自己也是刚接触Javascript。这里也只是分享其中一种,经过我自己测试后可行的方法。
应用场景
什么时候用到.json
以我在写一个Redis的可视化工具时碰到的问题为例子。
用户打开应用,在设置界面设置了自己Redis服务器的IP和密码。但是应用不可能一直挂在后台,迟早会被用户关闭。
那么问题来了,用户再次打开应用的时候,上一次设置的IP和密码会自动保存吗?当然不会,程序加载的是你预设好的代码,如果你在程序里预设IP为”127.0.0.1″,那么用户无论重启多少次应用,默认的IP还是”127.0.0.1″。
当然,让用户每次启动应用后都自行设置也没问题。但为了用户的良好体验,我就在想“怎么才能把用户上次输入的数据保存到本地”呢?
写入txt?然后每次都读取?写入是很方便,但是读取就很麻烦。每次读取txt都要去匹配然后分割字符串,代码实现起来实在是比较麻烦。
刚好最近在研究怎么爬取”深圳技术大学教务系统“的课表,初次接触到了Javascript和.json格式。发现.json文件过于好用了,C#自带处理Json格式方法,原本要自己写分割字符串的代码,现在人直接给你封装好直接用就完事了。
一个.json文件
WinUI3暂不“支持”
为什么说不支持捏?你打开你的项目,右键选择添加-新建项。搜索.json,发现只有TypeScript和Javascript的配置json。
但并不意味着WinUI3的程序无法读取.json,只是vs里的winui3项目不给你添加,仅此而已。
直接去文件夹下自己新建一个.json文件;或者用vs打开需要添加.json文件的文件夹(不要选择.sln进入项目),再选择添加-新建项,在web那一栏找到.json,选择添加,完事。
那么.json文件的格式到底是啥样的,我以我的InitSetting.json为例
[
{
"databaseIP": "127.0.0.1",
"databasePassword": null
}
]
该json文件指定了数据库的地址和密码。
.json存放的路径
非打包程序不要存放到C盘的AppData文件夹下
既然要读取.json文件,那得有它的路径吧,得有它的文件名吧?我个人建议把用户配置的.json存放到你程序的同个路径下。比如新建一个文件夹AppData,然后往里面扔.json。
个人建议不要(表示友好的词语,已自动屏蔽)存到C盘的AppData文件夹里,特别是选择打包(Packaged)方式发布的程序。先不说因为一堆应用都往AppData里面扔东西,我每次清这文件夹都要清理半天。
另一个最主要的原因是:选择未打包(Unpackaged)运行的程序,是无法通过
private static string ApplicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
等一系列方法获取到应用的环境文件夹(即C盘的AppData目录)的。这也是为什么你选择未打包运行程序的时候,你用这些方法会报错。因为应用在没打包的情况下压根不知道这个方法指向的是哪个文件夹。
读取.json
先读文件,再分字符串
以SimpleString为例,建议先参考该仓库的代码再来看接下来的内容。
在应用目录下有一个AppData文件夹,该文件夹中有一个InitSetting.json文件。该文件中存放着我想要在应用关闭后仍然保存的数据–数据库地址和数据库密码。
那么打开InitSetting.json的代码如下
private async void InitDatabase()
{
string fileName = "AppData/InitSettings.json";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,fileName);
string databaseIP = null;
string databasePassword = null;
if (File.Exists(filePath))
{
string json = await File.ReadAllTextAsync(filePath);
}
}
这段代码不难理解,单纯就是打开该json文件,并将文件里的所有内容都写入一个字符串中的操作。
接下来是拆分字符串。先来看看json文件的内容
[
{
"databaseIP": "127.0.0.1",
"databasePassword": null
}
]
对于json来说,方括号 “[ ]”就相当于一个起始标记,大括号”{ }”相当于一个数组,大括号里面是它的对象–即可以简单把它理解为一个C++的对象数组。
那么接下来我们就要用到”JsonArray”。将字符串划分为Json类型的数组。然后遍历数组去判断对应的值即可。代码如下
tring json = await File.ReadAllTextAsync(filePath);
JsonArray data = (JsonArray)JsonArray.Parse(json);
foreach (var item in data)
{
if(item["databaseIP"] != null)
databaseIP = item["databaseIP"].ToString();
if (item["databasePassword"] != null)
databasePassword = item["databasePassword"].ToString();
}
Settings.databaseInfo.databaseIP = databaseIP;
Settings.databaseInfo.databasePassword = databasePassword;
上面这种方法适用于:“寻找特定键名”的情况。
还有一种方法,适用“不需要考虑键名”的情况。该方法是我在“将json文件中的键值对写入Redis”中试出来的。这种方法用起来也会更加直观。
当然,这代码有点长,而且我没写注释。建议复制到vs里面看。代码如下
private async void JsonToRedis(object sender, RoutedEventArgs e)
{
string fileName = "AppData/RedisKey.json";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
try
{
configurationOptions = new ConfigurationOptions
{
EndPoints = { $"{Settings.databaseInfo.databaseIP}" },
Password = $"{Settings.databaseInfo.databasePassword}"
};
connectionMultiplexer = ConnectionMultiplexer.Connect(configurationOptions);
database = connectionMultiplexer.GetDatabase();
}
catch
{
ErrorMessage_CantFindRedis();
}
try
{
string keyName = null;
string keyValue = null;
long TTL = (long)TTLSetter.Value;
if (File.Exists(filePath))
{
string json = await File.ReadAllTextAsync(filePath);
JsonArray data = (JsonArray)JsonArray.Parse(json);
TimeSpan timeSpan;
if (TTLType == "s")
{
timeSpan = TimeSpan.FromSeconds((long)TTLSetter.Value);
}
else if (TTLType == "ms")
{
timeSpan = TimeSpan.FromMilliseconds((long)TTLSetter.Value);
}
else
{
timeSpan = TimeSpan.Zero;
}
foreach (var item in data)
{
var props = item.AsObject();
foreach (var prop in props)
{
keyName = prop.Key;
keyValue = prop.Value.ToString();
if (TTL != 0)
{
database.StringSet(keyName, keyValue, timeSpan);
}
else
{
database.StringSet(keyName, keyValue);
}
}
}
}
}
catch
{
ErrorMessage_UnsupportedMethod();
}
}
大致解释几点:
- try-catch是用来捕获异常的。防止应用在碰到异常时直接闪退。可以在catch的部分加上触发弹窗的代码表示程序运行时异常。
- Timespan类是用来处理时间的。主要是因为Redis中的键值对有设置TTL(Time to live)的操作。我想让用户能看到“选择设置61秒的TTL,在前端展示给用户的是设置了一分钟1秒的TTL”。所以用Timespan处理会方便许多。
- 和第一种方法不一样的是,第二种方法不需要考虑键名和键值的具体含义。所以foreach里面再套一个循环。去获取JsonArray中的每一个对象的键名和键值。
写者注
这部分真的建议自己写一遍。不同的程序json文件的含义和内容也大概率不一样。如果不能理解,可以自己照着Github上的代码改一下试试,多设几个断点调试调试就懂了。
删去其他无关代码,核心代码直接放出来的话,如下
string keyName = null;
string keyValue = null;
if (File.Exists(filePath))
{
string json = await File.ReadAllTextAsync(filePath);
JsonArray data = (JsonArray)JsonArray.Parse(json);
foreach (var item in data)
{
var props = item.AsObject();
foreach (var prop in props)
{
keyName = prop.Key;
keyValue = prop.Value.ToString();
}
}
}
}
写入json文件
读取的逆向操作
写入说白了就是读取的逆向操作。同样是“转换为Json对象,一次写入整个文档”。
先是序列化。首先要确认你的键名和键值是什么。还是以SimpleString为例子,我需要保存数据库地址和密码。那么键名就是数据库地址,键值就是数据库密码。那么现新建一个对象表示键值对。
public class DatabaseInfo
{
public string databaseIP { get; set; }
public string databasePassword { get; set; }
}
接下来就是对象的序列化了。
private void SaveIPAndPassword(object sender, RoutedEventArgs e)
{
string fileName = "AppData/InitSettings.json";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
try
{
List<DatabaseInfo>_data=new List<DatabaseInfo>();
_data.Add(new DatabaseInfo()
{
databaseIP = databaseInfo.databaseIP,
databasePassword = databaseInfo.databasePassword
});
string json = JsonSerializer.Serialize(_data);
File.WriteAllText(filePath, json);
}catch
{
ErrorMessage_CantSaveIPAndAddress();
}
}
其实最关键的就是这行代码。这行代码表示将_data对象序列化为字符串。
string json = JsonSerializer.Serialize(_data);
当然这只是代码示例,具体细节如何更改得看你的程序需要保存什么数据至本地,需要读取什么类型的数据,不过大体的思路都是差不多的。