Swift实现嵌套json字典重排序并输出string

news2025/3/30 22:41:33

在网络请求或接口签名中,通常要求将参数按照一定规则拼接成字符串。一个常见的做法是对字典的 key 进行排序,然后按照 “key=value” 的格式拼接,多个参数之间以特定符号(例如 &)连接。

如果参数中包含嵌套的字典或数组,则需要递归展开。这样的处理不仅可以保证字符串的唯一性,同时也方便后续的加密或签名操作。

  • 入参
    • 一个可以转成json的字典或者数组
    • 比如:["b": 2, "a": 1]
  • 返回值
    • 按照key进行排序后的字符串
    • 比如:a=1&b=2

下面的算法不仅支持简单的键值对,还能够递归处理嵌套结构,使得所有数据都能被有序地转换为字符串形式,从而满足不同业务场景下的参数签名需求。

为什么非要自己实现呢?

  1. 这是因为字典是无序的,一个key-value完全相等的字典,转成jsonString是不一样的。
  2. 平台差异,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:),用于处理数组。整个实现思路可以分为以下几个步骤:

  1. 字典的排序与遍历

    • 首先,对传入的字典的所有 key 进行排序,确保每次拼接后的结果一致。
    • 遍历排序后的 key,根据对应的 value 类型分别处理:
      • 如果 value 是字典,则递归调用自身,将字典内容转换为字符串;
      • 如果 value 是数组,则调用数组处理方法;
      • 如果 value 是基本类型(例如数字或字符串),直接转换为字符串;
      • 其他类型则按默认方式拼接,最后兜底。
  2. 数组的递归拼接

    • 数组中的每个元素可能又是字典或数组,需要递归调用对应的方法。
    • 每个元素转换后,通过逗号分隔,并在结束时移除多余的逗号。
  3. 格式规范

    • 字典的 key-value 对之间使用 & 符号分隔;
    • 数组的元素间使用 , 分隔,并在嵌套拼接时加上 {}[] 表示不同的数据结构。

代码实现

下面是完整的代码实现,并附有详细注释:


import Foundation

class SignTool {
    
    // 类方法,接收一个字典作为参数,返回拼接好的字符串
    class func concatenate(dictionary: [String: Any]) -> String {
        // 对字典的key进行排序
        let sortedKeys = dictionary.keys.sorted()
        var result = ""
        
        // 遍历排序后的key
        for (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)
    }
    
    
    @discardableResult
    func XCTAssertEqual(_ str1: String, _ str2: String) -> Bool {
        if str1 == str2 {
            print("✅")
            return true
        }
        print("❌ Expected: \(str1) but got: \(str2)")
        return false
    }
    
    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2322858.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Ai】--- 可视化 DeepSeek-r1 接入 Open WebUI(超详细)

在编程的艺术世界里,代码和灵感需要寻找到最佳的交融点,才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里,我们将共同追寻这种完美结合,为未来的世界留下属于我们的独特印记。【Ai】--- 可视化 DeepSeek-r1 接入 Open WebUI(超详细) 开发环境一、前情提要:你…

Flink基础简介和安装部署

文章目录 一、Flink基础简介1、什么是Flink2、Flink流处理特性3、Flink四大基石4、Flink中的角色 二、Flink集群搭建1、Local模式①上传Flink安装包②启动交互窗口③提交任务测试④访问WebUI页面查看⑤退出停止集群 2、Standalone模式①修改配置⽂件 conf/flink-conf.yaml②修改…

从零构建大语言模型全栈开发指南:第二部分:模型架构设计与实现-2.2.2文本生成逻辑:Top-k采样与温度控制

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 2.2.2 文本生成逻辑:Top-k采样与温度控制1. 文本生成的核心挑战与数学框架1.1 自回归生成的基本流程2. `Top-k`采样原理与工程实现2.1 数学定义与算法流程2.2 PyTorch实现优化3. 温度控制的数学本质与参…

LeetCode算法题(Go语言实现)_11

题目 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"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体…

matplotlib——南丁格尔玫瑰

南丁格尔玫瑰图&#xff08;Nightingale Rose Chart&#xff09;&#xff0c;是一种特殊形式的柱状图&#xff0c;它以南丁格尔&#xff08;Florence Nightingale&#xff09;命名&#xff0c;她在1858年首次使用这种图表来展示战争期间士兵死亡原因的数据。 它将数据绘制在极坐…

Django与网页表单

我叫补三补四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步 今天来讲一讲网页表单 网页表单又叫做HTML表单&#xff0c;用来处理用户从页面输入发送到服务器的数据&#xff0c;页面表单通常会提供复选框、单选按钮和文本字段&#xff0c;方便用户填写各种形式…

ChatDBA VS DeepSeek:快速诊断 OceanBase 集群新租户数据同步异常

社区王牌专栏《一问一实验&#xff1a;AI 版》改版以来已发布多期&#xff08;51-60&#xff09;&#xff0c;展现了 ChatDBA 在多种场景下解决问题的效果。 下面让我们正式进入《一问一实验&#xff1a;AI 版》第 62 期&#xff0c;看看 ChatDBA 最新效果以及与热门大模型 De…

Python----计算机视觉处理(Opencv:图像边缘检测:非极大值抑制,双阈值筛选)

一、 高斯滤波 边缘检测本身属于锐化操作&#xff0c;对噪点比较敏感&#xff0c;所以需要进行平滑处理。这里使用的是一个5*5的高斯 核对图像进行消除噪声。 二、计算图像的梯度和方向 三、非极大值抑制 在得到每个边缘的方向之后&#xff0c;其实把它们连起来边缘检测就算完了…

基于Kubernetes部署Prometheus监控平台

#作者&#xff1a;stackofumbrella 文章目录 prometheus和k8s集群版本对照表架构Prometheus Operator简介kube-prometheus下载地址 安装修改镜像地址修改Prometheus的service修改Grafana的service修改Alertmanager的service数据持久化执行安装 Prometheus验证Grafana验证解决C…

往期项目shader着色器实践效果应用合集

1、管路混色 2、水管水流效果 3、水管流入到流完效果 4、加热冷却 两 色混色 示意 XX、毒蘑菇测试效果

绿色暴政:Relax Max如何用军工科技定义环保新标准

《绿色暴政&#xff1a;Relax Max如何用军工科技定义环保新标准》 ——从隐形战斗机涂层到零碳卫浴的降维打击 &#xff08;洛克希德马丁实验室&#xff0c;2023年&#xff09;当F-35战斗机的隐形涂料配方被改写为卫浴釉料时&#xff0c;环保产业迎来了最硬核的颠覆者。Relax…

第十三届蓝桥杯单片机省赛程序设计试题

目录 试题 各程序块代码 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窗口&#xff0c;并且需要实现加载模型后重新渲染更新窗口的功能&#xff0c;但是一直无法更新被卡住了&#xff0c;现在把问题解决了总结一下整个实现过程。 创建一个自己的OpenGLWidget类 QOp…

ESP32驱动BMP280和MQ4传感器

文章目录 前言 一、硬件准备 所需组件 连接方式&#xff1a; 二、软件实现 1.所需库 2.代码实现 效果演示 三、上传Qt端 前言 在物联网和环境监测应用中&#xff0c;传感器是获取环境数据的关键组件。本文将详细介绍如何使用ESP32微控制器同时驱动BMP280大气压力传感器…

MQTT协议笔记

消息格式 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的消息协议&#xff0c;专为低带宽、高延迟或不可靠的网络设计&#xff0c;广泛应用于物联网&#xff08;IoT&#xff09;设备之间的通信。MQTT消息体的结构遵循MQTT协议规范&#xff0…

“征服HTML引号恶魔:“完全解析手册”!!!(quot;表示双引号)

&#x1f6a8;&#x1f4e2; "征服HTML引号恶魔&#xff1a;“完全解析手册” &#x1f4e2;&#x1f6a8; &#x1f3af; 博客引言&#xff1a;当引号变成"恶魔" &#x1f631; 是否遇到过这种情况&#xff1a; 写HTML时满心欢喜输入<div title"他…

如何使用VS中的Android Game Development Extension (AGDE) 来查看安卓 Logcat 日志

一、首先按照以下 指引 中的 第1、2步骤&#xff0c;安装一下 AGDE &#xff0c;AGDE 的安装包可以在官网上找到。 UE4 使用AndroidGameDevelopmentExtension&#xff08;AGDE&#xff09;对安卓客户端做“断点调试”与“代码热更”-CSDN博客 在执行第二步骤前&#xff0c;记得…

VSCode 生成HTML 基本骨架

在VSCode 新建html文件中敲一个英文感叹号 ! <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><titl…

【Spring AI】基于专属知识库的RAG智能问答小程序开发——功能优化:用户鉴权相关工具类代码

系列文章目录 【Spring AI】基于专属知识库的RAG智能问答小程序开发——完整项目&#xff08;含完整前端后端代码&#xff09;【Spring AI】基于专属知识库的RAG智能问答小程序开发——代码逐行精讲&#xff1a;核心ChatClient对象相关构造函数【Spring AI】基于专属知识库的R…