Swift实现嵌套json字典重排序并输出string
在网络请求或接口签名中,通常要求将参数按照一定规则拼接成字符串。一个常见的做法是对字典的 key 进行排序,然后按照 “key=value” 的格式拼接,多个参数之间以特定符号(例如 &)连接。
如果参数中包含嵌套的字典或数组,则需要递归展开。这样的处理不仅可以保证字符串的唯一性,同时也方便后续的加密或签名操作。
- 入参
- 一个可以转成json的字典或者数组
- 比如:["b": 2, "a": 1]
- 返回值
- 按照key进行排序后的字符串
-
比如:a=1&b=2
下面的算法不仅支持简单的键值对,还能够递归处理嵌套结构,使得所有数据都能被有序地转换为字符串形式,从而满足不同业务场景下的参数签名需求。
为什么非要自己实现呢?
- 这是因为字典是无序的,一个key-value完全相等的字典,转成jsonString是不一样的。
- 平台差异,iOS平台转成的json字符串和Java平台、go平台生成的字符串规则是不一样的,需要一个规则把无序的对象处理成有序的字符串
同一个平台,对同一个字典,生成json字符串结果都不一样。比如:
func testOrign() {let dictionary: [String: Any] = ["b": [1,["d": 4, "c": 3]],"a": ["e": 5,"f": [6, 7],"uid": 8,"age": 20.124,"student": true,"teacher": false,"dic":["rID":"123","author":"gcs",]],"image": ["10", "11", "12", "15"],"array":[[1,2,3],["1","2","3"],["rID":"123","author":"gcs"],["rID":"1234","author":"g"],]]// 把这个字典转出josn字符串,然后再转回字典,看看是否和原来的字典一样let jsonData = try! JSONSerialization.data(withJSONObject: dictionary, options: [])let jsonString = String(data: jsonData, encoding: .utf8)!print("jsonString",jsonString)do {let dictionary2 = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]let jsonData2 = try! JSONSerialization.data(withJSONObject: dictionary2, options: [])let jsonString2 = String(data: jsonData2, encoding: .utf8)!print("jsonString2",jsonString2)}}
实现思路
SignTool 类的核心在于两个方法:一个是用于处理字典的 concatenate(dictionary:),另一个是私有方法 concatenate(array:),用于处理数组。整个实现思路可以分为以下几个步骤:
-
字典的排序与遍历
- 首先,对传入的字典的所有 key 进行排序,确保每次拼接后的结果一致。
- 遍历排序后的 key,根据对应的 value 类型分别处理:
- 如果 value 是字典,则递归调用自身,将字典内容转换为字符串;
- 如果 value 是数组,则调用数组处理方法;
- 如果 value 是基本类型(例如数字或字符串),直接转换为字符串;
- 其他类型则按默认方式拼接,最后兜底。
-
数组的递归拼接
- 数组中的每个元素可能又是字典或数组,需要递归调用对应的方法。
- 每个元素转换后,通过逗号分隔,并在结束时移除多余的逗号。
-
格式规范
- 字典的 key-value 对之间使用
&符号分隔; - 数组的元素间使用
,分隔,并在嵌套拼接时加上{}或[]表示不同的数据结构。
- 字典的 key-value 对之间使用
代码实现
下面是完整的代码实现,并附有详细注释:
import Foundationclass SignTool {// 类方法,接收一个字典作为参数,返回拼接好的字符串class func concatenate(dictionary: [String: Any]) -> String {// 对字典的key进行排序let sortedKeys = dictionary.keys.sorted()var result = ""// 遍历排序后的keyfor (index, key) in sortedKeys.enumerated() {if let value = dictionary[key] {// 如果value是字典类型,递归调用concatenate方法if let valueDict = value as? [String: Any] {result += "\(key)={\(self.concatenate(dictionary: valueDict))}"// 如果value是数组类型,递归调用concatenate方法} else if let valueArray = value as? [Any] {result += "\(key)=[\(self.concatenate(array: valueArray))]"// 如果value是NSNumber类型,直接拼接} else if let numberValue = value as? NSNumber {result += "\(key)=\(numberValue)"// 如果value是字符串类型,直接拼接} else if let stringValue = value as? String {result += "\(key)=\(stringValue)"// 其他类型的value,直接拼接} else {result += "\(key)=\(value)"}}// 在key-value对之间添加&符号if index < sortedKeys.count - 1 {result += "&"}}return result}// 私有类方法,接收一个数组作为参数,返回拼接好的字符串private class func concatenate(array: [Any]) -> String {var result = ""// 遍历数组中的每个元素for value in array {// 如果元素是字典类型,递归调用concatenate方法if let valueDict = value as? [String: Any] {result += "{\(self.concatenate(dictionary: valueDict))},"// 如果元素是数组类型,递归调用concatenate方法} else if let valueArray = value as? [Any] {result += "[\(self.concatenate(array: valueArray))],"// 如果元素是NSNumber类型,直接拼接} else if let numberValue = value as? NSNumber {result += "\(numberValue),"// 如果元素是字符串类型,直接拼接} else if let stringValue = value as? String {result += "\(stringValue),"// 其他类型的元素,直接拼接} else {result += "\(value),"}}// 移除最后的逗号if result.hasSuffix(",") {result.removeLast()}return result}
}
实际效果
通过上述实现,可以将复杂的字典与数组数据转换为统一的字符串格式。例如:

输出结果类似于:
a={age=20.124&e=5&f=[6,7]&student=1&teacher=0&uid=8}&b=[1,{c=3&d=4}]&image=[10,11,12,15]
其他测试用例及代码:
class SignToolTests: NSObject {public static func test() {print("start test")let test = SignToolTests()test.testConcatenateWithSimpleDictionary()test.testConcatenateWithNestedDictionary()test.testConcatenateWithArray()test.testConcatenateWithNestedArray()test.testConcatenateWithComplexStructure()test.testConcatenate()test.testMax()print("end test")}func testConcatenateWithSimpleDictionary() {let dictionary: [String: Any] = ["b": 2, "a": 1]let result = SignTool.concatenate(dictionary: dictionary)XCTAssertEqual(result, "a=1&b=2")}func testConcatenateWithNestedDictionary() {let dictionary: [String: Any] = ["b": ["d": 4, "c": 3], "a": 1]let result = SignTool.concatenate(dictionary: dictionary)XCTAssertEqual(result, "a=1&b={c=3&d=4}")}func testConcatenateWithArray() {let dictionary: [String: Any] = ["b": [1, 2, 3], "a": 1]let result = SignTool.concatenate(dictionary: dictionary)XCTAssertEqual(result, "a=1&b=[1,2,3]")}func testConcatenateWithNestedArray() {let dictionary: [String: Any] = ["b": [1,["d": 4,"c": 3]],"a": 1]let result = SignTool.concatenate(dictionary: dictionary)XCTAssertEqual(result, "a=1&b=[1,{c=3&d=4}]")}func testConcatenateWithComplexStructure() {let dictionary: [String: Any] = ["b": [1,["d": 4, "c": 3]],"a": ["e": 5,"f": [6, 7]]]let result = SignTool.concatenate(dictionary: dictionary)XCTAssertEqual(result, "a={e=5&f=[6,7]}&b=[1,{c=3&d=4}]")}func testConcatenate() {let dictionary: [String: Any] = ["b": [1,["d": 4, "c": 3]],"a": ["e": 5,"f": [6, 7],"uid": 8,"age": 20.124,"student": true,"teacher": false,],"image": ["10", "11", "12", "15"],]let result1 = SignTool.concatenate(dictionary: dictionary)// 把这个字典转出josn字符串,然后再转回字典,看看是否和原来的字典一样let jsonData = try! JSONSerialization.data(withJSONObject: dictionary, options: [])let jsonString = String(data: jsonData, encoding: .utf8)!let dict = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]let result2 = SignTool.concatenate(dictionary: dict)print("jsonString",jsonString)print("result1",result1)print("result2",result2)XCTAssertEqual(result1, result2)}func testMax() {let dictionary: [String: Any] = ["b": [1,["d": 4, "c": 3]],"a": ["e": 5,"f": [6, 7],"uid": 8,"age": 20.124,"student": true,"teacher": false,"dic":["roomID":"123","author":"gcs",]],"image": ["10", "11", "12", "15"],"array":[[1,2,3],["1","2","3"],["roomID":"123","author":"gcs"],["roomID":"1234","author":"g"],]]let result1 = SignTool.concatenate(dictionary: dictionary)// 把这个字典转出josn字符串,然后再转回字典,看看是否和原来的字典一样let jsonData = try! JSONSerialization.data(withJSONObject: dictionary, options: [])let jsonString = String(data: jsonData, encoding: .utf8)!let dict = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]let result2 = SignTool.concatenate(dictionary: dict)print("jsonString",jsonString)print("result1",result1)print("result2",result2)XCTAssertEqual(result1, result2)}@discardableResultfunc XCTAssertEqual(_ str1: String, _ str2: String) -> Bool {if str1 == str2 {print("✅")return true}print("❌ Expected: \(str1) but got: \(str2)")return false}}

相关文章:
Swift实现嵌套json字典重排序并输出string
在网络请求或接口签名中,通常要求将参数按照一定规则拼接成字符串。一个常见的做法是对字典的 key 进行排序,然后按照 “keyvalue” 的格式拼接,多个参数之间以特定符号(例如 &)连接。 如果参数中包含嵌套的字典或…...
【Ai】--- 可视化 DeepSeek-r1 接入 Open WebUI(超详细)
在编程的艺术世界里,代码和灵感需要寻找到最佳的交融点,才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里,我们将共同追寻这种完美结合,为未来的世界留下属于我们的独特印记。【Ai】--- 可视化 DeepSeek-r1 接入 Open WebUI(超详细) 开发环境一、前情提要:你…...
VSCode加Cline插件加DeepSeek实现AI编程指南
VSCode加Cline插件加DeepSeek实现AI编程指南 简介 本文将详细介绍如何在VSCode中使用Cline插件结合DeepSeek AI实现高效的AI辅助编程,特别适合初学者快速上手。我们将通过实现一个TodoList应用的例子来演示整个流程。 环境准备 1. 安装VSCode 前往VSCode官网下…...
代码规范之Variable Names变量名
代码规范之Variable Names变量名 golang中 官方文档:https://go.dev/wiki/CodeReviewComments#variable-names Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCoun…...
2025春招市场迎AI热潮:生成式人工智能(GAI)认证如何重构人才竞争力
随着科技的飞速发展,人工智能(AI)已逐渐渗透到我们生活的方方面面,从智能家居到自动驾驶,从智能客服到医疗诊断,AI的身影无处不在。而在这股AI浪潮中,生成式人工智能(Generative AI,…...
Flink基础简介和安装部署
文章目录 一、Flink基础简介1、什么是Flink2、Flink流处理特性3、Flink四大基石4、Flink中的角色 二、Flink集群搭建1、Local模式①上传Flink安装包②启动交互窗口③提交任务测试④访问WebUI页面查看⑤退出停止集群 2、Standalone模式①修改配置⽂件 conf/flink-conf.yaml②修改…...
SpringBoot分布式项目实战:观察者模式的高阶应用与避坑指南
一、痛点场景:当观察者遇上分布式 在某电商平台重构项目中,我们遭遇了这样的困境:订单中心完成支付后需要触发库存扣减、积分结算、物流调度等12个后续操作。最初的实现采用了硬编码调用: // 伪代码示例 public void paySuccess…...
How to use pgbench to test performance for PostgreSQL?
pgbench 是一个用于测试 PostgreSQL 数据库性能的基准测试工具。通过模拟多个客户端并发执行 SQL 查询,它可以帮助你评估数据库的性能。以下是使用 pgbench 的基本步骤: 安装 pgbench pgbench 是 PostgreSQL 的一部分,因此在安装 PostgreSQ…...
C#基础学习(五)函数中的ref和out
1. 引言:为什么需要ref和out? 问题背景:函数参数默认按值传递,值类型在函数内修改不影响外部变量;引用类型重新赋值时外部对象不变。核心作用:允许函数内部修改外部变量的值,实现“双向传参…...
从零构建大语言模型全栈开发指南:第二部分:模型架构设计与实现-2.2.2文本生成逻辑:Top-k采样与温度控制
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 2.2.2 文本生成逻辑:Top-k采样与温度控制1. 文本生成的核心挑战与数学框架1.1 自回归生成的基本流程2. `Top-k`采样原理与工程实现2.1 数学定义与算法流程2.2 PyTorch实现优化3. 温度控制的数学本质与参…...
关于CodeJava的学习笔记——9
一、IO流 1、定义 IInput输入 OOutput输出 流 : 数据从源点传输到汇点的"管道"而已 2、流的分类 按照方向分: 输入流 输出流 *:参照物当前Java程序为参照物 按照单位分: 字节流 字符流 按照功能分: 节点流 过滤流(包装流 处…...
LeetCode算法题(Go语言实现)_11
题目 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列&a…...
Python----数据分析(足球运动员数据分析)
一、数据展示 1.1、数据 1.2、列名 字段名备注Name姓名Nationality国籍National_Position国家队位置National_Kit国家队号码Club所在俱乐部Club_Position所在俱乐部位置Club_Kit俱乐部号码Club_Joining加入俱乐部时间Contract_Expiry合同到期时间Rating评分Height身高Weight体…...
Day38 | 1365. 有多少小于当前数字的数字、941. 有效的山脉数组、1207. 独一无二的出现次数、283. 移动零、189. 轮转数组
1365. 有多少小于当前数字的数字 题目链接:1365. 有多少小、于当前数字的数字 - 力扣(LeetCode) 题目难度:简单 代码: class Solution {public int[] smallerNumbersThanCurrent(int[] nums) {Map<Integer,Inte…...
Docker-清理容器空间prune
docker system prune -a 是一个非常有用的命令,用于清理 Docker 系统中未使用的资源,包括停止的容器、未使用的网络、卷以及未被任何容器引用的镜像(悬空镜像和所有未使用的镜像)。以下是关于该命令的详细说明: 命令格…...
matplotlib——南丁格尔玫瑰
南丁格尔玫瑰图(Nightingale Rose Chart),是一种特殊形式的柱状图,它以南丁格尔(Florence Nightingale)命名,她在1858年首次使用这种图表来展示战争期间士兵死亡原因的数据。 它将数据绘制在极坐…...
Django与网页表单
我叫补三补四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲网页表单 网页表单又叫做HTML表单,用来处理用户从页面输入发送到服务器的数据,页面表单通常会提供复选框、单选按钮和文本字段,方便用户填写各种形式…...
Rust从入门到精通之入门篇:10.包和模块
包和模块 在本章中,我们将学习 Rust 的包和模块系统,它们是组织和重用代码的重要工具。随着项目规模的增长,良好的代码组织变得越来越重要,Rust 提供了一套强大的机制来管理代码结构。 包和 Crate Crate Crate 是 Rust 中最高…...
ChatDBA VS DeepSeek:快速诊断 OceanBase 集群新租户数据同步异常
社区王牌专栏《一问一实验:AI 版》改版以来已发布多期(51-60),展现了 ChatDBA 在多种场景下解决问题的效果。 下面让我们正式进入《一问一实验:AI 版》第 62 期,看看 ChatDBA 最新效果以及与热门大模型 De…...
dify忘记密码
特别好,非常好,一把年纪忘了dify的账号、密码了,very good!!! 参考如下教程 https://zhuanlan.zhihu.com/p/24515387167 rootbae577d82ec7:/# psql -U postgres psql: error: connection to server on so…...
Python----计算机视觉处理(Opencv:图像边缘检测:非极大值抑制,双阈值筛选)
一、 高斯滤波 边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理。这里使用的是一个5*5的高斯 核对图像进行消除噪声。 二、计算图像的梯度和方向 三、非极大值抑制 在得到每个边缘的方向之后,其实把它们连起来边缘检测就算完了…...
vue3(笔记)5.0--pinia工具的知识扩展
pinia工具 defineStore(创建pinia) 作用:用于定义一个 Pinia store。 用法: 接收一个唯一的 ID 和一个配置对象,配置对象中可以定义 state、getters 和 actions。state 是一个函数,返回初始状态。getters 类似于 Vue 组件中的计…...
基于Kubernetes部署Prometheus监控平台
#作者:stackofumbrella 文章目录 prometheus和k8s集群版本对照表架构Prometheus Operator简介kube-prometheus下载地址 安装修改镜像地址修改Prometheus的service修改Grafana的service修改Alertmanager的service数据持久化执行安装 Prometheus验证Grafana验证解决C…...
往期项目shader着色器实践效果应用合集
1、管路混色 2、水管水流效果 3、水管流入到流完效果 4、加热冷却 两 色混色 示意 XX、毒蘑菇测试效果...
如何在 React 项目中使用React.lazy和Suspense实现组件的懒加载?
大白话如何在 React 项目中使用React.lazy和Suspense实现组件的懒加载? 在 React 项目里,有时候组件功能多、体积大,要是一次性把所有组件都加载进来,网页加载速度就会变慢。而 React 提供了 React.lazy 和 Suspense 这两个好东西…...
绿色暴政:Relax Max如何用军工科技定义环保新标准
《绿色暴政:Relax Max如何用军工科技定义环保新标准》 ——从隐形战斗机涂层到零碳卫浴的降维打击 (洛克希德马丁实验室,2023年)当F-35战斗机的隐形涂料配方被改写为卫浴釉料时,环保产业迎来了最硬核的颠覆者。Relax…...
蓝桥杯刷题 Day 4 栈与链表
蓝桥杯刷题 Day 4 栈与链表 文章目录 蓝桥杯刷题 Day 4 栈与链表前言一、栈1. 解题思路2. 拆解代码(不复杂,不拆了) 二、链表1. 解题思路1.1 主函数1.2 自定义列表类1.2.1 插入操作1.2.2 删除操作1.2.3 按要求输出 三、 题后收获3.1 知识点 前…...
第十三届蓝桥杯单片机省赛程序设计试题
目录 试题 各程序块代码 init.c main.c other.h other.c key.c seg.c onewire.c部分 ds1302.c部分 试题 各程序块代码 init.c #include "other.h"void init74hc138(unsigned char n){P2(P2&0x1f)|(n<<5);P2&0x1f; } void init(){P00x00;in…...
QOpenGLWidget动态加载功能实现教程(Qt+OpenGL)
QOpenGLWidget动态加载功能实现教程 我需要在Qt里面使用QOpenGLWidget显示OpenGL窗口,并且需要实现加载模型后重新渲染更新窗口的功能,但是一直无法更新被卡住了,现在把问题解决了总结一下整个实现过程。 创建一个自己的OpenGLWidget类 QOp…...
机器学习正则化技术:Ridge、Lasso与ElasticNet全解析
机器学习中的正则化技术 在机器学习中,正则化技术(如 Ridge 和 Lasso)主要用于解决过拟合问题,通过限制模型复杂度提高泛化能力。以下是详细说明及实例代码: 一、正则化解决的问题 过拟合:模型在训练集表…...

