使用 Codable 序列化为 JSON 时的 Swift 字符串转义

Posted

技术标签:

【中文标题】使用 Codable 序列化为 JSON 时的 Swift 字符串转义【英文标题】:Swift String escaping when serializing to JSON using Codable 【发布时间】:2018-04-15 00:54:46 【问题描述】:

我正在尝试将我的对象序列化如下:

import Foundation

struct User: Codable 
    let username: String
    let profileURL: String


let user = User(username: "John", profileURL: "http://google.com")

let json = try? JSONEncoder().encode(user)

if let data = json, let str = String(data: data, encoding: .utf8) 
    print(str)

但是在 macOS 上,我得到以下信息:

"profileURL":"http:\/\/google.com","username":"John"

(注意转义的“/”字符)。

在 Linux 机器上我得到:

"username":"John","profileURL":"http://google.com"

如何让 JSONEncoder 返回未转义的?

我需要 JSON 中的字符串严格不转义。

【问题讨论】:

别担心。你的字符串没有问题 这不是一个选项,因为我必须签署数据并验证签名。有多余的字符会使签名无效 还要注意\/是一个有效 JSON转义序列,比较json.org。 马丁说得对。 JSON 规范要求允许对斜杠进行转义。听起来您的服务器不兼容 JSON。话虽如此,如果您确实需要,可以将数据中所有出现的“\/”替换为“/”。 @tofiffe:你甚至不能依赖键/值对的顺序,也可能有额外的空格。这也会破坏签名吗? – 如果所有这些事情都很重要,您可能必须编写自己的 JSON 编码器。 【参考方案1】:

适用于 iOS 13+ / macOS 10.15+ 您可以在 json 解码器中使用 .withoutEscapingSlashes 选项来避免转义斜杠

let user = User(username: "John", profileURL: "http://google.com")

let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .withoutEscapingSlashes
let json = try? jsonEncoder.encode(user)

if let data = json, let str = String(data: data, encoding: .utf8) 
    print(str)

控制台 O/P

"profileURL":"http://google.com","username":"John"


注意:正如 Martin R 在 cmets 中提到的,\/ 是一个有效的 JSON 转义序列。

【讨论】:

有没有办法在 ios 12 上达到同样的效果? 其他答案可能对您有所帮助。【参考方案2】:

我明白了。问题是它不包含任何 \ 字符。这只是 swift 的属性,它总是会在控制台上返回这样的字符串。解决方法是 j-son 解析它。

不过,您可以在下面使用“/”字符串替换“\/”的解决方案

 let newString = str.replacingOccurrences(of: "\\/", with: "/") 
 print(newString)

【讨论】:

正如我在问题中所展示的,它并不总是返回转义字符串(它在 Linux 机器上工作正常)。解析 JSON 后,问题就消失了,但我需要 JSON 与服务器上的相同【参考方案3】:

我最终使用了replacingOccurrences(of:with:),这可能不是最好的解决方案,但它解决了问题:

import Foundation

struct User: Codable 
    let username: String
    let profileURL: String


let user = User(username: "John", profileURL: "http://google.com")

let json = try? JSONEncoder().encode(user)

if let data = json, let str = String(data: data, encoding: .utf8)?.replacingOccurrences(of: "\\/", with: "/") 
    print(str)
    dump(str)

【讨论】:

感谢您的回答(尽管这不是非常令人满意),很抱歉您遇到了所有这些人没有回答您的问题,而是希望您提出不同的问题。 谢谢。这仍然有效。可惜没有更优雅的解决方案。 JSONEncoder API 必须更改以允许这样做?人们会喊“JSON 标准允许转义”。但我有截止日期和不喜欢转义字符的服务器。【参考方案4】:

在玩 JSONEncoder/JSONDecoder 时, 我发现URL 类型在编码->解码时是有损的。

使用相对于另一个 URL 的字符串进行初始化。

init?(string: String, relativeTo: URL?)

可能对这个苹果文档有帮助:https://developer.apple.com/documentation/foundation/url

使用PropertyList 版本,但是:

let url = URL(string: "../", relativeTo: URL(string: "http://google.com"))! 
let url2 = PropertyListDecoder().decode([URL].self, from: PropertyListEncoder().encode([User]))

其他方式

let url = URL(string: "../", relativeTo: URL(string: "http://google.com"))! 
let url2 = JSONDecoder().decode([URL].self, from: JSONEncoder().encode([User]))

希望对你有帮助!!

【讨论】:

【参考方案5】:

实际上你不能这样做,因为在 macOS 和 Linux 中转义系统有点不同。在 linux // 上是允许的,macOS - 不允许(它使用 NSSerialization)。所以,你可以在你的字符串上添加百分比编码,这样可以保证你在 macOS 和 linux 上的字符串相等,正确的字符串发布到服务器并正确验证。在添加百分比转义集CharacterSet.urlHostAllowed。可以这样做:

init(name: String, profile: String)
        username = name
        if let percentedString = profile.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed)
            profileURL = percentedString
        else
            profileURL = ""
        
    

同样的方式,你可以去掉PercentEncoding 而且你不需要修改服务器端!!!

【讨论】:

如前所述,json是签名的,添加百分比编码会改变签名和哈希 @tofiffe 您不能按原样发送 url 作为参数。在服务器端添加删除百分比(您不能)或发送带有转义符号的 profileUrl。即使在 Linux 上,如果您创建这样的字符串,也不能保证您可以按原样发送到服务器。如果您将其发送到带有转义符号的服务器,它将更改配置文件 url 的哈希值,对吗?那又怎样,每次发送百分比字符串来验证它什么的。

以上是关于使用 Codable 序列化为 JSON 时的 Swift 字符串转义的主要内容,如果未能解决你的问题,请参考以下文章

将json字符反序列化为枚举

将枚举成员序列化为 JSON

如何将sql查询序列化为json?

Jackson 使用枚举键、POJO 值反序列化为 Map

反序列化json树结构并设置父项

如何将具有嵌套属性的 JSON 对象反序列化为 Symfony 实体?