# JSON 工具

MT JSON 库提供了轻量高效的 JSON 解析和生成功能,基于 minimal-json 实现,并为 UI 组件提供了数据双向绑定的便捷方法。

# 概述

MT JSON 库具有以下特点:

  • 轻量高效:基于 minimal-json 库实现,体积小、性能优
  • 简洁易用:提供链式调用的 API,代码简洁清晰
  • UI 数据绑定:提供 UI 组件与 JSON 数据的双向绑定功能
  • 类型安全:完整的类型判断和转换方法

主要功能包括:

  • 解析 JSON 字符串
  • 创建和修改 JSON 对象和数组
  • UI 组件数据的自动保存和恢复
  • 格式化输出 JSON 文本

# 核心类

# JSON

JSON 工具类提供了创建 JSON 值和解析 JSON 字符串的静态方法。

# 创建 JSON 值

// 创建基本类型值
JSONValue value1 = JSON.value(true);        // 布尔值
JSONValue value2 = JSON.value(42);          // 整数
JSONValue value3 = JSON.value(3.14);        // 浮点数
JSONValue value4 = JSON.value("Hello");     // 字符串

// 创建空对象和数组
JSONObject obj = JSON.object();
JSONArray arr = JSON.array();

方法列表:

  • value(boolean) - 创建布尔值
  • value(int) - 创建整数值
  • value(long) - 创建长整数值
  • value(float) - 创建浮点数值
  • value(double) - 创建双精度浮点数值
  • value(String) - 创建字符串值
  • array() - 创建空 JSON 数组
  • object() - 创建空 JSON 对象

# 常量

JSONValue nullValue = JSON.NULL;    // JSON null 值
JSONValue trueValue = JSON.TRUE;    // JSON true 值
JSONValue falseValue = JSON.FALSE;  // JSON false 值

# 解析 JSON

// 从字符串解析
String jsonText = "{\"name\":\"MT\",\"version\":3}";
JSONObject obj = JSON.parse(jsonText).asObject();
// 也可以直接使用 JSONObject 的构造函数
JSONObject obj2 = new JSONObject(jsonText);

// 从 Reader 解析
Reader reader = new FileReader("config.json");
JSONValue value = JSON.parse(reader);

方法列表:

  • parse(String) - 解析 JSON 字符串
  • parse(Reader) - 从 Reader 解析 JSON

# JSONObject

JSONObject 表示一个 JSON 对象,包含一组键值对。

# 添加和设置值

JSONObject obj = new JSONObject();

// add() 方法 - 快速添加,不检查重复键
obj.add("name", "MT Manager")
   .add("version", 3)
   .add("enabled", true);

// put() 方法 - 设置值,会替换已存在的键
obj.put("version", 4);  // 更新 version 的值

// 移除键值对
obj.remove("enabled");

add() 和 put() 的区别:

  • add() 不检查键是否已存在,直接追加到末尾,性能更高,适合构建新对象
  • put() 会查找并替换已存在的键,性能较低,适合修改现有对象

方法列表:

  • add(String name, JSONValue value) - 添加 JSON 值
  • add(String name, int/long/float/double/boolean/String value) - 添加基本类型值
  • put(String name, JSONValue value) - 设置 JSON 值
  • put(String name, int/long/float/double/boolean/String value) - 设置基本类型值
  • remove(String name) - 移除指定键

# 获取值

JSONObject obj = new JSONObject("{\"name\":\"MT\",\"age\":3,\"active\":true}");

// 获取 JSONValue
JSONValue value = obj.get("name");

// 获取基本类型(带默认值)
String name = obj.getString("name", "Unknown");
int age = obj.getInt("age", 0);
boolean active = obj.getBoolean("active", false);

// 获取嵌套对象和数组
JSONObject nested = obj.getJSONObject("config");
JSONArray items = obj.getJSONArray("items");

方法列表:

  • get(String name) - 获取 JSONValue,不存在时返回 null
  • getBoolean(String name, boolean defaultValue) - 获取布尔值
  • getInt(String name, int defaultValue) - 获取整数
  • getLong(String name, long defaultValue) - 获取长整数
  • getFloat(String name, float defaultValue) - 获取浮点数
  • getDouble(String name, double defaultValue) - 获取双精度浮点数
  • getString(String name, String defaultValue) - 获取字符串
  • getJSONObject(String name) - 获取嵌套对象
  • getJSONArray(String name) - 获取数组

# 查询方法

JSONObject obj = new JSONObject();
obj.add("key1", "value1");

boolean exists = obj.contains("key1");  // true
boolean empty = obj.isEmpty();          // false
int count = obj.size();                 // 1
List<String> keys = obj.names();        // ["key1"]

方法列表:

  • contains(String name) - 检查键是否存在
  • isEmpty() - 检查是否为空对象
  • size() - 获取键值对数量
  • names() - 获取所有键名列表

# UI 组件快捷方法

JSONObject 提供了将 UI 组件数据写入 JSON 的便捷方法,这些方法使用组件的 ID 作为 JSON 的键名。

JSONObject data = new JSONObject();

// 保存 UI 组件数据到 JSON
data.putText(editText);              // 保存编辑框文本
data.putChecked(checkBox);           // 保存复选框选中状态
data.putSelection(spinner);          // 保存下拉框选中位置
data.putCheckedPosition(radioGroup); // 保存单选组选中位置
data.putCheckedId(radioGroup);       // 保存单选组选中按钮 ID

方法列表:

  • putText(PluginEditText) - 将编辑框文本写入 JSON
  • putChecked(PluginCompoundButton) - 将复合按钮选中状态写入 JSON
  • putSelection(PluginSpinner) - 将下拉框选中位置写入 JSON
  • putCheckedPosition(PluginRadioGroup) - 将单选组选中位置写入 JSON
  • putCheckedId(PluginRadioGroup) - 将单选组选中按钮 ID 写入 JSON

注意:这些方法要求 UI 组件必须已设置 ID(通过 Builder 的 id() 方法),否则会抛出异常。

# 序列化

JSONObject obj = new JSONObject();
obj.add("name", "MT").add("version", 3);

// 转换为最小化 JSON 字符串
String json = obj.toString();
// 结果:{"name":"MT","version":3}

// 转换为格式化的 JSON 字符串
String prettyJson = obj.toString(WriterConfig.PRETTY_PRINT);
// 结果:
// {
//   "name": "MT",
//   "version": 3
// }

// 写入到 Writer
Writer writer = new FileWriter("config.json");
obj.writeTo(writer, WriterConfig.PRETTY_PRINT);
writer.close();

方法列表:

  • toString() - 转换为最小化 JSON 字符串
  • toString(WriterConfig) - 使用指定配置转换为 JSON 字符串
  • writeTo(Writer) - 写入到 Writer(最小化格式)
  • writeTo(Writer, WriterConfig) - 使用指定配置写入到 Writer

# JSONArray

JSONArray 表示一个 JSON 数组,包含一组有序的 JSON 值。

# 添加值

JSONArray arr = new JSONArray();

// 添加基本类型值
arr.add(1)
   .add(2.5)
   .add("text")
   .add(true);

// 添加 JSON 值
arr.add(JSON.object().add("key", "value"));
arr.add(JSON.array().add(1).add(2));

方法列表:

  • add(JSONValue value) - 添加 JSON 值
  • add(int/long/float/double/boolean/String value) - 添加基本类型值

# 设置值

JSONArray arr = new JSONArray();
arr.add(1).add(2).add(3);

// 替换指定索引的值
arr.set(1, 20);  // 将索引 1 的值从 2 改为 20

方法列表:

  • set(int index, JSONValue value) - 设置指定索引的 JSON 值
  • set(int index, int/long/float/double/boolean/String value) - 设置指定索引的基本类型值

# 获取值

JSONArray arr = new JSONArray("[1, 2.5, \"text\", true]");

// 获取 JSONValue
JSONValue value = arr.get(0);

// 获取基本类型
int num = arr.getInt(0);           // 1
double decimal = arr.getDouble(1); // 2.5
String text = arr.getString(2);    // "text"
boolean flag = arr.getBoolean(3);  // true

// 获取嵌套对象和数组
JSONObject obj = arr.getJSONObject(4);
JSONArray nested = arr.getJSONArray(5);

方法列表:

  • get(int index) - 获取指定索引的 JSONValue
  • getBoolean(int index) - 获取布尔值
  • getInt(int index) - 获取整数
  • getLong(int index) - 获取长整数
  • getFloat(int index) - 获取浮点数
  • getDouble(int index) - 获取双精度浮点数
  • getString(int index) - 获取字符串
  • getJSONObject(int index) - 获取嵌套对象
  • getJSONArray(int index) - 获取嵌套数组

# 查询和操作

JSONArray arr = new JSONArray();
arr.add(1).add(2).add(3);

boolean empty = arr.isEmpty();  // false
int length = arr.size();        // 3

// 移除指定索引的元素
arr.remove(1);  // 移除索引 1 的元素(值为 2)

方法列表:

  • isEmpty() - 检查是否为空数组
  • size() - 获取数组长度
  • remove(int index) - 移除指定索引的元素

# 序列化

JSONArray 的序列化方法与 JSONObject 相同:

JSONArray arr = new JSONArray();
arr.add(1).add(2).add(3);

// 转换为 JSON 字符串
String json = arr.toString();  // [1,2,3]

// 格式化输出
String prettyJson = arr.toString(WriterConfig.PRETTY_PRINT);
// 结果:
// [
//   1,
//   2,
//   3
// ]

# JSONValue

JSONValue 是所有 JSON 值的基类,提供类型判断和转换方法。

# 类型判断

JSONValue value = JSON.parse("42");

if (value.isNumber()) {
    int num = value.asInt();
}

// 其他类型判断
boolean isObj = value.isObject();      // 是否为对象
boolean isArr = value.isArray();       // 是否为数组
boolean isStr = value.isString();      // 是否为字符串
boolean isNum = value.isNumber();      // 是否为数值
boolean isBool = value.isBoolean();    // 是否为布尔值
boolean isNull = value.isNull();       // 是否为 null
boolean isTrue = value.isTrue();       // 是否为 true
boolean isFalse = value.isFalse();     // 是否为 false

# 类型转换

JSONValue value = JSON.parse("\"Hello\"");

// 转换为具体类型
String str = value.asString();         // "Hello"
JSONObject obj = value.asObject();     // 如果不是对象则抛出异常
JSONArray arr = value.asArray();       // 如果不是数组则抛出异常
int num = value.asInt();               // 如果不是数值则抛出异常
long lng = value.asLong();
float flt = value.asFloat();
double dbl = value.asDouble();
boolean bool = value.asBoolean();

注意:如果类型转换失败(例如将字符串转换为对象),会抛出 UnsupportedOperationException 异常。

# WriterConfig

WriterConfig 控制 JSON 输出的格式。

# 预定义配置

// 最小化输出(默认)
String compact = obj.toString(WriterConfig.MINIMAL);
// {"name":"MT","version":3}

// 格式化输出(带缩进和换行)
String pretty = obj.toString(WriterConfig.PRETTY_PRINT);
// {
//   "name": "MT",
//   "version": 3
// }

可用配置:

  • WriterConfig.MINIMAL - 最小化输出,无空格、无换行
  • WriterConfig.PRETTY_PRINT - 格式化输出,带 2 个空格缩进和换行

# UI 组件数据绑定

MT JSON 库为 UI 组件提供了数据双向绑定功能,可以快速实现配置的保存和恢复。

# JSON → UI(Builder 方法)

UI 组件的 Builder 类提供了从 JSON 读取数据并配置组件的便捷方法。这些方法使用组件的 ID 作为键名从 JSON 中读取数据。

# PluginEditTextBuilder

从 JSON 读取文本内容并设置到编辑框:

JSONObject savedData = loadConfig();  // 假设从文件加载的配置

pluginUIBuilder
    .addEditText("username")
    .hint("请输入用户名")
    .text(savedData)  // 从 savedData 读取 "username" 字段
    ...

方法列表:

  • text(JSONObject data) - 从 JSON 读取文本内容
  • text(JSONObject data, String defaultValue) - 从 JSON 读取文本内容,提供默认值

# PluginSpinnerBuilder

从 JSON 读取选中位置并设置到下拉框:

List<String> languages = Arrays.asList("简体中文", "English", "日本語");

pluginUIBuilder
    .addSpinner("language")
    .items(languages)
    .selection(savedData)  // 从 savedData 读取 "language" 字段
    ...

方法列表:

  • selection(JSONObject data) - 从 JSON 读取选中位置
  • selection(JSONObject data, int defaultValue) - 从 JSON 读取选中位置,提供默认值

# PluginBaseCompoundButtonBuilder

从 JSON 读取选中状态并设置到复合按钮(CheckBox、Switch、RadioButton 等):

pluginUIBuilder
    .addCheckBox("remember_me")
    .text("记住密码")
    .checked(savedData)  // 从 savedData 读取 "remember_me" 字段
    ...

方法列表:

  • checked(JSONObject data) - 从 JSON 读取选中状态
  • checked(JSONObject data, boolean defaultValue) - 从 JSON 读取选中状态,提供默认值

# PluginRadioGroupBuilder

从 JSON 读取选中项并设置到单选组:

pluginUIBuilder
    .addRadioGroup(true)
    .id("theme")
    .children(builder -> builder
        .addRadioButton("light").text("浅色主题")
        .addRadioButton("dark").text("深色主题")
        .addRadioButton("auto").text("自动")
    )
    .checkedId(savedData)  // 从 savedData 读取 "theme" 字段(选中按钮的 ID)
    ...

方法列表:

  • checkedId(JSONObject data) - 从 JSON 读取选中按钮 ID
  • checkedId(JSONObject data, String defaultValue) - 从 JSON 读取选中按钮 ID,提供默认值
  • checkedPosition(JSONObject data) - 从 JSON 读取选中位置(索引)
  • checkedPosition(JSONObject data, int defaultValue) - 从 JSON 读取选中位置,提供默认值

# UI → JSON(JSONObject 方法)

JSONObject 提供了从 UI 组件提取数据并写入 JSON 的便捷方法。这些方法使用组件的 ID 作为键名。

# 工作原理

// 假设有以下 UI 组件
PluginEditText editUsername = pluginUI.requireViewById("username");
PluginCheckBox checkRemember = pluginUI.requireViewById("remember_me");
PluginSpinner spinnerLanguage = pluginUI.requireViewById("language");

// 创建 JSON 对象并保存 UI 数据
JSONObject data = new JSONObject();
data.putText(editUsername);       // 使用 "username" 作为键名
data.putChecked(checkRemember);   // 使用 "remember_me" 作为键名
data.putSelection(spinnerLanguage); // 使用 "language" 作为键名

// 保存到文件
saveConfig(data);

方法说明:

  • putText(PluginEditText editText) - 将编辑框的文本内容写入 JSON
  • putChecked(PluginCompoundButton button) - 将复合按钮的选中状态写入 JSON
  • putSelection(PluginSpinner spinner) - 将下拉框的选中位置写入 JSON
  • putCheckedPosition(PluginRadioGroup group) - 将单选组的选中位置写入 JSON
  • putCheckedId(PluginRadioGroup group) - 将单选组的选中按钮 ID 写入 JSON

重要:这些方法要求 UI 组件必须已设置 ID。如果组件没有 ID,putText()putChecked() 等方法会调用 requireId() 并抛出异常。

# 使用示例

# 示例 1:解析和生成 JSON

展示基本的 JSON 解析、创建和格式化操作:

// 解析 JSON 字符串
String jsonText = "{\"name\":\"MT Manager\",\"version\":3,\"plugins\":[\"翻译\",\"编辑器\"]}";
JSONObject config = new JSONObject(jsonText);

// 访问数据
String name = config.getString("name", "");
int version = config.getInt("version", 0);
JSONArray plugins = config.getJSONArray("plugins");

// 遍历数组
for (int i = 0; i < plugins.size(); i++) {
    String plugin = plugins.getString(i);
    pluginContext.log("插件:" + plugin);
}

// 创建新的 JSON 对象
JSONObject newConfig = new JSONObject();
newConfig.add("name", "我的插件")
         .add("version", 1)
         .add("enabled", true);

// 创建嵌套对象
JSONObject settings = new JSONObject();
settings.add("theme", "dark")
        .add("fontSize", 14);
newConfig.add("settings", settings);

// 创建数组
JSONArray features = new JSONArray();
features.add("功能A").add("功能B").add("功能C");
newConfig.add("features", features);

// 输出格式化的 JSON
String output = newConfig.toString(WriterConfig.PRETTY_PRINT);
pluginContext.log(output);
// {
//   "name": "我的插件",
//   "version": 1,
//   "enabled": true,
//   "settings": {
//     "theme": "dark",
//     "fontSize": 14
//   },
//   "features": [
//     "功能A",
//     "功能B",
//     "功能C"
//   ]
// }

# 示例 2:UI 数据双向绑定

展示完整的配置保存和恢复流程:

public class ConfigDialog {

    private final PluginContext context;
    private final PluginUI pluginUI;
    private final File configFile;

    public ConfigDialog(PluginContext context) {
        this.context = context;
        this.pluginUI = PluginUI.get(context);
        this.configFile = new File(context.getFilesDir(), "config.json");
    }

    public void show() {
        // 加载配置
        JSONObject savedData = loadConfig();

        // 创建对话框 UI
        pluginUI.buildDialog()
            .setTitle("设置")
            .setView(builder -> builder
                .addTextView("基本设置").textSize(16).bold()
                .addEditText("username")
                    .id("username")
                    .hint("用户名")
                    .text(savedData)  // 从 JSON 加载文本
                .addEditText("email")
                    .id("email")
                    .hint("邮箱")
                    .inputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS)
                    .text(savedData)  // 从 JSON 加载文本

                .addSpace()
                .addTextView("偏好设置").textSize(16).bold()
                .addSpinner("language")
                    .id("language")
                    .items(Arrays.asList("简体中文", "English", "日本語"))
                    .selection(savedData, 0)  // 从 JSON 加载选中位置,默认为 0

                .addCheckBox("notifications")
                    .id("notifications")
                    .text("接收通知")
                    .checked(savedData, true)  // 从 JSON 加载选中状态,默认为 true

                .addCheckBox("auto_update")
                    .id("auto_update")
                    .text("自动更新")
                    .checked(savedData, false)

                .addSpace()
                .addTextView("主题设置").textSize(16).bold()
                .addRadioGroup(true)
                    .id("theme")
                    .children(b -> b
                        .addRadioButton("light").text("浅色主题")
                        .addRadioButton("dark").text("深色主题")
                        .addRadioButton("auto").text("跟随系统")
                    )
                    .checkedId(savedData, "auto")  // 从 JSON 加载选中 ID,默认为 "auto"
            )
            .setPositiveButton("{ok}", (dialog, which) -> {
                // 收集 UI 数据
                saveConfig(dialog);
            })
            .setNegativeButton("{cancel}", null)
            .show();
    }

    private JSONObject loadConfig() {
        if (!configFile.exists()) {
            return new JSONObject();
        }

        try {
            Reader reader = new FileReader(configFile);
            JSONObject data = JSON.parse(reader).asObject();
            reader.close();
            return data;
        } catch (Exception e) {
            context.log("加载配置失败:" + e.getMessage());
            return new JSONObject();
        }
    }

    private void saveConfig(PluginDialog dialog) {
        try {
            // 创建 JSON 对象
            JSONObject data = new JSONObject();

            // 从 UI 组件收集数据
            data.putText(dialog.requireViewById("username"));
            data.putText(dialog.requireViewById("email"));
            data.putSelection(dialog.requireViewById("language"));
            data.putChecked(dialog.requireViewById("notifications"));
            data.putChecked(dialog.requireViewById("auto_update"));
            data.putCheckedId(dialog.requireViewById("theme"));

            // 保存到文件
            Writer writer = new FileWriter(configFile);
            data.writeTo(writer, WriterConfig.PRETTY_PRINT);
            writer.close();

            context.showToast("保存成功");
        } catch (Exception e) {
            context.log("保存配置失败:" + e.getMessage());
            context.showToast("保存失败");
        }
    }
}

# 示例 3:嵌套 JSON 数据处理

展示处理复杂嵌套 JSON 结构的方法:

// 创建嵌套的 JSON 结构
JSONObject root = new JSONObject();

// 添加基本信息
root.add("name", "示例项目")
    .add("version", "1.0.0");

// 添加作者信息(嵌套对象)
JSONObject author = new JSONObject();
author.add("name", "张三")
      .add("email", "zhangsan@example.com");
root.add("author", author);

// 添加依赖列表(嵌套数组)
JSONArray dependencies = new JSONArray();
dependencies.add(new JSONObject()
    .add("name", "库A")
    .add("version", "2.0.0"));
dependencies.add(new JSONObject()
    .add("name", "库B")
    .add("version", "1.5.3"));
root.add("dependencies", dependencies);

// 访问嵌套数据
String projectName = root.getString("name", "");
JSONObject authorInfo = root.getJSONObject("author");
String authorName = authorInfo.getString("name", "");
String authorEmail = authorInfo.getString("email", "");

// 遍历依赖列表
JSONArray deps = root.getJSONArray("dependencies");
for (int i = 0; i < deps.size(); i++) {
    JSONObject dep = deps.getJSONObject(i);
    String depName = dep.getString("name", "");
    String depVersion = dep.getString("version", "");
    pluginContext.log(depName + " " + depVersion);
}

// 修改嵌套数据
authorInfo.put("email", "newemail@example.com");

// 添加新的依赖
JSONObject newDep = new JSONObject();
newDep.add("name", "库C").add("version", "3.0.0");
deps.add(newDep);

// 输出完整的 JSON
String output = root.toString(WriterConfig.PRETTY_PRINT);
pluginContext.log(output);

# 注意事项

  1. 线程安全:MT JSON 库不是线程安全的。如果多个线程同时访问和修改 JSONObject 或 JSONArray,必须在外部进行同步。

  2. UI 组件 ID 要求:使用 UI 组件快捷方法(putText()putChecked() 等)时,必须确保组件已通过 Builder 的 id() 方法设置了 ID,否则会抛出异常。

  3. Builder 方法的行为:Builder 的 JSON 方法(如 text(JSONObject))不会尝试本地化转换,这与直接设置字符串值的 text(String) 方法不同。如果需要本地化文本,应使用 {key} 格式。

  4. 默认值的使用getInt()getString() 等方法都提供了带默认值参数的重载版本。当 JSON 中不存在指定的键时,会返回默认值而不是抛出异常。

  5. 类型转换异常:使用 asObject()asArray() 等类型转换方法时,如果类型不匹配会抛出 UnsupportedOperationException。建议先使用 isObject()isArray() 等方法进行类型检查。

  6. 数组索引越界:访问 JSONArray 时,如果索引超出范围会抛出 IndexOutOfBoundsException。建议先使用 size() 方法检查数组长度。

  7. 格式化输出的性能WriterConfig.PRETTY_PRINT 会增加输出的体积(增加空格和换行符),仅建议在调试或需要人类可读的场景下使用。生产环境中应使用 WriterConfig.MINIMAL

  8. add() 和 put() 的选择:构建新对象时使用 add() 性能更高;修改已有对象时使用 put() 确保不会产生重复键。

# 相关接口