App Inventor 2 JSON数据解析教程 - 解析API返回的JSON数据

« 返回首页

什么是JSON

JSON(JavaScript Object Notation,JavaScript对象表示法)是一种轻量级的数据交换格式,广泛用于Web API的数据传输。几乎所有现代API接口都使用JSON格式返回数据。

一个典型的JSON数据示例:

{
  "name": "Tim Beaver",
  "age": 25,
  "enrolled": true,
  "school": {
    "name": "MIT",
    "city": "Cambridge"
  },
  "courses": ["Math", "Physics", "CS"]
}

JSON中的数据类型

JSON类型 示例 App Inventor对应类型
字符串(String) "hello" 文本(text)
数字(Number) 42, 3.14 数字(number)
布尔值(Boolean) true, false 布尔值(boolean)
数组(Array) [1, 2, 3] 列表(list)
对象(Object) {"key": "value"} 字典(dictionary)

核心要点: JSON对象({})在App Inventor中对应字典(dictionary),JSON数组([])对应列表(list)。

核心工具

在App Inventor 2中解析JSON,需要用到以下两个核心工具:

1. Web组件

Web组件用于发送HTTP请求获取API数据。它位于「连接」类别中。

关键方法:

  • Get — 发送HTTP GET请求
  • GotText事件 — 请求完成时触发,参数包括 responseCode(状态码)和 responseContent(响应内容)

2. 字典积木块

字典积木块位于「字典」类别中,是解析JSON的核心工具。

关键积木块:

  • get value for key — 通过键名获取字典中的值
  • get value at key path — 通过键路径获取嵌套数据
  • is a dictionary? — 判断是否为字典类型
  • list by walking key path — 批量提取嵌套数据

3. JsonTextDecodeWithDictionaries方法

这是Web组件提供的方法,专门用于将JSON文本转换为App Inventor的字典/列表结构。

⚠️ 注意:请使用 JsonTextDecodeWithDictionaries 而非 JsonTextDecode。后者将JSON对象转换为列表对(旧版行为),前者转换为字典(推荐行为)。

解析简单JSON

假设API返回如下简单JSON:

{
  "userId": 1,
  "id": 1,
  "title": "Hello World",
  "body": "This is a test post."
}

步骤

  1. 使用Web组件发送GET请求
  2. 在GotText事件中,用 JsonTextDecodeWithDictionaries 解析响应内容
  3. 用 get value for key 提取具体字段

积木块伪代码

when Web1.GotText(url, responseCode, responseType, responseContent) {
  if responseCode == 200 {
    var jsonData = Web1.JsonTextDecodeWithDictionaries(responseContent)
    var title = getDictionaryValue(jsonData, "title")
    var body = getDictionaryValue(jsonData, "body")
    Label1.Text = title
    Label2.Text = body
  } else {
    Label1.Text = join("请求失败,状态码:", responseCode)
  }
}

要点说明

  • responseCode = 200 表示请求成功
  • JsonTextDecodeWithDictionaries 返回一个字典对象
  • get value for key 通过键名(如 "title")获取对应的值
  • 如果键不存在,get value for key 返回 not found 参数的默认值

解析嵌套JSON

实际API返回的数据往往是多层嵌套的。例如:

{
  "id": 1,
  "name": "Tim the Beaver",
  "school": {
    "name": "Massachusetts Institute of Technology",
    "city": "Cambridge"
  },
  "enrolled": true,
  "classes": ["6.001", "18.01", "8.01"]
}

这里 school 的值又是一个JSON对象(字典),classes 的值是一个JSON数组(列表)。

方法一:逐层获取

when Web1.GotText(url, responseCode, responseType, responseContent) {
  if responseCode == 200 {
    var data = Web1.JsonTextDecodeWithDictionaries(responseContent)
    
    // 第一层:获取name
    var name = getDictionaryValue(data, "name")
    
    // 第二层:先获取school字典,再从中获取name
    var school = getDictionaryValue(data, "school")
    var schoolName = getDictionaryValue(school, "name")
    
    Label1.Text = name
    Label2.Text = schoolName
  }
}

方法二:使用 get value at key path(推荐)

get value at key path 可以通过一个路径列表一次性深入嵌套结构,更加简洁:

when Web1.GotText(url, responseCode, responseType, responseContent) {
  if responseCode == 200 {
    var data = Web1.JsonTextDecodeWithDictionaries(responseContent)
    
    // 使用键路径直接获取嵌套值
    var schoolName = getDictionaryValueAtPath(data, createList("school", "name"))
    
    Label1.Text = schoolName
  }
}

路径 ["school", "name"] 的含义:先取 school 键的值(一个字典),再从该字典中取 name 键的值。

解析JSON数组

JSON数组在App Inventor中会被解析为列表。当JSON中包含数组时,可以用列表操作来处理。

示例数据

{
  "people": [
    {"first_name": "Tim", "last_name": "Beaver"},
    {"first_name": "John", "last_name": "Smith"},
    {"first_name": "Jane", "last_name": "Doe"}
  ]
}

方法一:使用 for each 循环

when Web1.GotText(url, responseCode, responseType, responseContent) {
  if responseCode == 200 {
    var data = Web1.JsonTextDecodeWithDictionaries(responseContent)
    var people = getDictionaryValue(data, "people")
    var names = createList()
    
    foreach person in people {
      var firstName = getDictionaryValue(person, "first_name")
      addItemsToList(names, firstName)
    }
    
    Label1.Text = joinItems(names, ", ")
  }
}

方法二:使用 list by walking key path(高级技巧)

list by walking key path 配合 walk all at level 可以一步到位:

提取所有 first_name:

global data = Web1.JsonTextDecodeWithDictionaries(global_responseContent)
global firstNames = walkKeyPath(global_data, createList("people", "walk all at level", "first_name"))
// firstNames = ["Tim", "John", "Jane"]

解释:

  1. "people" — 进入 people 键,得到一个列表
  2. walk all at level — 遍历列表中的每一个元素(每个字典)
  3. "first_name" — 从每个字典中取 first_name 键的值

混合字典和数组的路径

如果 JSON 数据为:

{
  "id": 1,
  "name": "Tim the Beaver",
  "classes": ["6.001", "18.01", "8.01"]
}

要获取第二门课程("18.01"),可以用数字索引:

global secondClass = getDictionaryValueAtPath(global_data, createList("classes", 2))
// secondClass = "18.01"

注意:App Inventor的列表索引从 1 开始,不是从 0 开始。

实战案例:调用天气API获取天气数据

需求描述

调用一个免费的天气API,获取指定城市的天气信息并显示。

UI设计

组件 名称 用途
TextBox 文本输入框_城市 输入城市名
Button 按钮_查询 查询按钮
Label 标签_温度 显示温度
Label DescLabel 显示天气描述
Label HumidityLabel 显示湿度
Web Web客户端1 网络请求组件

API说明

以 wttr.in 为例(免费天气API,无需API Key):

https://wttr.in/Beijing?format=j1

返回的JSON结构(简化版):

{
  "current_condition": [
    {
      "temp_C": "25",
      "weatherDesc": [{"value": "Sunny"}],
      "humidity": "45",
      "FeelsLikeC": "27"
    }
  ],
  "nearest_area": [
    {
      "areaName": [{"value": "Beijing"}],
      "country": [{"value": "China"}]
    }
  ]
}

完整积木块伪代码

按钮点击 — 发送请求:

when Button1.Click() {
  var city = TextBox1.Text
  if city != "" {
    Web1.Url = join("https://wttr.in/", city, "?format=j1")
    Web1.Get()
  } else {
    Label1.Text = "请输入城市名"
  }
}

接收响应 — 解析JSON:

when Web1.GotText(url, responseCode, responseType, responseContent) {
  if responseCode == 200 {
    
    // 第一步:将JSON文本解析为字典
    var data = Web1.JsonTextDecodeWithDictionaries(responseContent)
    
    // 第二步:获取 current_condition 数组的第一个元素
    var conditions = getDictionaryValue(data, "current_condition")
    var current = selectListItem(conditions, 1)
    
    // 第三步:从当前天气字典中提取各字段
    var temp = getDictionaryValue(current, "temp_C")
    var humidity = getDictionaryValue(current, "humidity")
    var feelsLike = getDictionaryValue(current, "FeelsLikeC")
    
    // 第四步:获取天气描述(嵌套更深一层)
    var descList = getDictionaryValue(current, "weatherDesc")
    var descItem = selectListItem(descList, 1)
    var desc = getDictionaryValue(descItem, "value")
    
    // 第五步:显示结果
    Label1.Text = join("温度:", temp, "°C(体感 ", feelsLike, "°C)")
    DescLabel.Text = join("天气:", desc)
    HumidityLabel.Text = join("湿度:", humidity, "%")
    
  } else {
    Label1.Text = join("请求失败: ", responseCode)
  }
}

使用 get value at key path 简化

上面的步骤二、三可以简化为:

// 直接用键路径获取温度
global temp = getDictionaryValueAtPath(global_data, createList("current_condition", 1, "temp_C"))

// 直接用键路径获取湿度
global humidity = getDictionaryValueAtPath(global_data, createList("current_condition", 1, "humidity"))

使用 list by walking key path 提取多个城市数据

如果返回了多个城市的数据(数组),可以批量提取:

global allTemps = walkKeyPath(global_data, createList("current_condition", "walk all at level", "temp_C"))
// 得到所有城市的温度列表

常用字典积木块参考

创建字典

global dict = createDictionary(createPair("name", "Alice"), createPair("age", 20))

查询操作

积木块 功能 示例
get value for key 通过键获取值 get "name" → "Alice"
get value at key path 通过路径获取嵌套值 get ["school", "name"] → "MIT"
is key in dictionary? 判断键是否存在 "name" in dict? → true
get keys 获取所有键 → ["name", "age"]
get values 获取所有值 → ["Alice", 20]
size of dictionary 获取键值对数量 → 2

修改操作

积木块 功能
set value for key 设置键值(不存在则创建)
set value for key path 设置嵌套键值
delete entry for key 删除指定键

类型判断

积木块 功能
is a dictionary? 判断是否为字典
is a list? 判断是否为列表

常见错误与解决方法

1. 使用了 JsonTextDecode 而非 JsonTextDecodeWithDictionaries

错误现象: 解析JSON对象后得到的是列表 (("key1" "value1") ("key2" "value2")) 而非字典,无法使用 get value for key。

解决方法: 始终使用 JsonTextDecodeWithDictionaries。JsonTextDecode 是旧版方法,将JSON对象转换为列表对形式(与 lookup in pairs 配合使用),不推荐用于新项目。

2. 忘记检查 responseCode

错误现象: 直接解析 responseContent,但请求可能失败(404、500等),此时 responseContent 不是有效JSON。

解决方法: 始终先判断 responseCode = 200 再解析:

when Web1.GotText(url, responseCode, responseType, responseContent) {
  if responseCode == 200 {
    // 解析JSON
  } else {
    // 提示错误
  }
}

3. 键名拼写错误

错误现象: get value for key 返回 “not found”,但JSON中确实有数据。

解决方法: 仔细核对键名,注意大小写。JSON键名是区分大小写的。"Temp_C" 和 "temp_C" 是不同的键。

4. 混淆列表和字典

错误现象: 对列表使用 get value for key 或对字典使用 select list item,导致运行时错误。

解决方法:

  • JSON数组([])→ 列表 → 用 select list item 按索引访问
  • JSON对象({})→ 字典 → 用 get value for key 按键名访问
  • 可用 is a dictionary? 和 is a list? 先判断类型

5. 索引越界

错误现象: 访问列表中不存在的索引(如对3个元素的列表取第4项)。

解决方法: 先用 length of list 检查列表长度,或使用 is in list 判断。

6. 中文编码问题

错误现象: 返回的中文显示为乱码。

解决方法: 使用 PostTextWithEncoding 并指定 UTF-8 编码;或在收到响应后,确保使用正确的文本编码。App Inventor默认使用UTF-8,通常不会有此问题。

7. 异步请求陷阱

错误现象: 在调用 Web客户端1.Get() 之后立即访问全局变量,但数据还没回来。

解决方法: Web请求是异步的。所有数据操作必须在 GotText 事件中完成,不能在调用 Get() 之后立即处理数据。

// ❌ 错误示范
when Button1.Click() {
  Web1.Get()
  Label1.Text = global_result // 此时数据还没返回!
}

// ✅ 正确做法
when Button2.Click() {
  Web1.Get()
}

when Web1.GotText(url, responseCode, responseType, responseContent) {
  // 在这里处理返回的数据
  Label1.Text = global_parsedResult
}

调试技巧

1. 打印JSON原始内容

在 GotText 事件中,先将 responseContent 显示到Label或通过 Notifier 弹窗显示,确认返回了什么数据:

when Web1.GotText(url, responseCode, responseType, responseContent) {
  Label1.Text = responseContent
}

2. 检查解析后的类型

global data = Web1.JsonTextDecodeWithDictionaries(global_responseContent)
if isDictionary(global_data) {
  Label1.Text = "这是一个字典"
} else if isList(global_data) {
  Label1.Text = "这是一个列表"
}

3. 查看字典的所有键

global keys = getDictionaryKeys(global_data)
Label1.Text = joinItems(global_keys, ", ")
// 输出所有键名,帮助定位正确的字段

总结

场景 使用方法
解析JSON文本 Web客户端1.JsonTextDecodeWithDictionaries(jsonText)
获取简单字段 get value for key: "key名" 从字典: dict
获取嵌套字段 get value at key path: ["层级1", "层级2", ...] 从字典: dict
遍历JSON数组 for each item in list 循环
批量提取字段 list by walking key path + walk all at level
发送GET请求 Web客户端1.Get()
发送POST请求 Web客户端1.PostText(jsonText)

推荐流程: 调用API → 检查状态码 → 解析JSON → 提取数据 → 显示结果


本文参考了 MIT App Inventor官方文档 - Dictionaries 和 Using Web APIs with JSON。


© 2025 AppInventor2中文网. 本教程基于 MIT App Inventor 官方文档编写,遵循 Creative Commons Attribution-ShareAlike 4.0 International License 协议。

文档反馈