如何用Chainlink Any API将JSON数组转为Solidity结构体列表?
问题描述
我有一个返回如下JSON数组的API:
[ { "id": 2435, "name": "First Last", "email": "xxx@gmail.com", "gender": "male", "status": "inactive" }, ... ]
我尝试使用Chainlink Any API将响应填充到合约的结构体列表变量(users)中,以下是我的合约代码:
pragma solidity ^0.8.7; import '@chainlink/contracts/src/v0.8/ChainlinkClient.sol'; import '@chainlink/contracts/src/v0.8/ConfirmedOwner.sol'; contract FetchFromArray is ChainlinkClient, ConfirmedOwner { using Chainlink for Chainlink.Request; string public jsonData; bytes32 private jobId; uint256 private fee; User[] public users; struct User{ uint256 id; uint256 name; uint256 email; uint256 gender; uint256 status; } event GetUsersData(bytes32 indexed requestId, string jsonData); /** * @notice Initialize the link token and target oracle */ constructor() ConfirmedOwner(msg.sender) { setChainlinkToken(0x326C977E6efc84E512bB9C30f76E30c160eD06FB); setChainlinkOracle(0xCC79157eb46F5624204f47AB42b3906cAA40eaB7); jobId = '7d80a6386ef543a3abb52817f6707e3b'; fee = (1 * LINK_DIVISIBILITY) / 10; // 0.1 * 10**18 (Varies by network and job) } /** * Create a Chainlink request to retrieve API response */ function getUser() public returns (bytes32 requestId){ Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector); // Set the URL to perform the GET request on req.add('get', 'https://gorest.co.in/public/v2/users'); req.add('path', '$'); // Sends the request return sendChainlinkRequest(req, fee); } /** * Receive the response in the form of string */ function fulfill(bytes32 _requestId, string memory _jsonData) public recordChainlinkFulfillment(_requestId) { emit GetUsersData(_requestId,_jsonData); jsonData = _jsonData; // users = TO DO populate users list } /** * Allow withdraw of Link tokens from the contract */ function withdrawLink() public onlyOwner { LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress()); require(link.transfer(msg.sender, link.balanceOf(address(this))), 'Unable to transfer'); } }
我遇到两个具体问题:
- 使用什么JSONPath表达式可以获取API的完整JSON响应?我试过
req.add('path', '$')和req.add('path', '*'),但都无法返回JSON。 - 获取完整JSON并存储到
jsonData变量后,如何解析它并将结果插入到User[] public users变量中?
解决方案
问题1:获取完整JSON响应的正确方式
你当前使用的Chainlink Job大概率不支持返回完整JSON字符串——多数默认HTTP Job的设计目标是提取单个字段(数值/字符串),而非返回整个响应体。
解决步骤:
- 更换适配的Job:针对你合约配置的Polygon Mumbai网络,可使用支持返回完整JSON字符串的Job ID,比如
29fa9aa13bf1468788b7cc4a500a45b8(该Job专门用于将整个API响应以string类型返回)。 - JSONPath保持
$:$是正确的JSONPath表达式,代表整个JSON根节点,不需要修改。
问题2:解析JSON并填充到User数组
Solidity本身没有内置JSON解析能力,且链上解析大数组会产生极高Gas成本,优先推荐用Chainlink External Adapter在链下完成解析,再将结构化数据传给合约。
方案1:使用External Adapter(生产环境首选)
1. 修正合约结构体
首先要纠正你合约里的类型错误:name、email、gender、status都是字符串类型,不能定义为uint256,需改为string:
struct User{ uint256 id; string name; string email; string gender; string status; }
2. 开发External Adapter
编写一个链下服务,完成以下逻辑:
- 调用目标API获取JSON数组
- 解析数组中的每个用户对象,提取
id、name等字段 - 将整理后的批量用户数据返回给Chainlink Oracle
3. 调整合约请求与fulfill函数
更换为适配External Adapter的Job后,修改fulfill函数以接收解析后的批量数据,直接填充到users数组:
// 示例:接收批量用户字段数组 function fulfill( bytes32 _requestId, uint256[] memory ids, string[] memory names, string[] memory emails, string[] memory genders, string[] memory statuses ) public recordChainlinkFulfillment(_requestId) { emit GetUsersData(_requestId, "Fulfilled users data"); // 清空原有数据(可选) delete users; // 批量添加用户到数组 for(uint256 i = 0; i < ids.length; i++){ users.push(User({ id: ids[i], name: names[i], email: emails[i], gender: genders[i], status: statuses[i] })); } }
方案2:链上解析(仅测试场景使用)
如果必须在链上解析,可使用OpenZeppelin提供的JSONParser工具库,但注意Gas成本问题:
- 导入库:
import "@openzeppelin/contracts/utils/json/JSONParser.sol";
- 修改fulfill函数实现解析逻辑:
function fulfill(bytes32 _requestId, string memory _jsonData) public recordChainlinkFulfillment(_requestId) { emit GetUsersData(_requestId, _jsonData); jsonData = _jsonData; // 解析JSON数组 (bool success, JSONParser.List memory usersList) = JSONParser.parseList(_jsonData); require(success, "Failed to parse JSON array"); delete users; // 遍历数组解析每个用户对象 for(uint256 i = 0; i < usersList.length(); i++){ (success, JSONParser.Object memory userObj) = usersList.getObject(i); require(success, "Failed to fetch user object"); // 解析各字段 (success, uint256 id) = userObj.getUint("id"); require(success, "Failed to parse id"); (success, string memory name) = userObj.getString("name"); require(success, "Failed to parse name"); (success, string memory email) = userObj.getString("email"); (success, string memory gender) = userObj.getString("gender"); (success, string memory status) = userObj.getString("status"); // 添加到users数组 users.push(User({ id: id, name: name, email: email, gender: gender, status: status })); } }
内容的提问来源于stack exchange,提问作者aminedev
相关产品推荐
相关产品推荐

