当前位置: 首页 > article >正文

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

在网络请求或接口签名中,通常要求将参数按照一定规则拼接成字符串。一个常见的做法是对字典的 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.kler.cn/a/600624.html

相关文章:

  • 树状数组模板
  • leetcode 之(移除元素)
  • 分布式架构-Spring技术如何能实现分布式事务
  • Matlab教程002:Matlab变量和基本运算符的使用
  • 【Redis】深入解析 Redis 五大数据结构
  • 【构建性能分析插件设计与实现:打造前端项目的性能透视镜】
  • Ubuntu Server版本Ubuntu 24.04.2 LTS下载与安装-详细教程,细致到每一步都有说明
  • 如何在jupyter notebook中使用django框架
  • 隔空打印,IPP,IPD,HP Jetdirect协议的区别(Mac添加打印机四种协议的区别)
  • 科学计算(2):矩阵特征值计算
  • 【Linux系统篇】:进程流水线的精密齿轮--创建,替换,终止与等待的工业级管控艺术
  • 【机器学习】线性回归和逻辑回归的区别在哪?
  • 什么是 LLM(大语言模型)?——从直觉到应用的全面解读
  • PHP eval 长度限制绕过与 Webshell 获取
  • 【linux】ubuntu 用户管理
  • javaSE.多维数组
  • 开发中后端返回下划线数据,要不要统一转驼峰?
  • CUDA 学习(3)——CUDA 初步实践
  • 基于Flask的通用登录注册模块,并代理跳转到目标网址
  • ANYmal Parkour: Learning Agile Navigation for Quadrupedal Robots