Swift 中对象的自动 JSON 序列化和反序列化

Posted

技术标签:

【中文标题】Swift 中对象的自动 JSON 序列化和反序列化【英文标题】:Automatic JSON serialization and deserialization of objects in Swift 【发布时间】:2014-11-08 18:43:17 【问题描述】:

我正在寻找一种在 Swift 中自动序列化和反序列化类实例的方法。假设我们已经定义了以下类……

class Person 
    let firstName: String
    let lastName: String

    init(firstName: String, lastName: String) 
        self.firstName = firstName
        self.lastName = lastName
    

… 和Person 实例:

let person = Person(firstName: "John", lastName: "Doe")

person 的 JSON 表示形式如下:


    "firstName": "John",
    "lastName": "Doe"

现在,这是我的问题:

    如何序列化 person 实例并获取上述 JSON,而无需手动将类的所有属性添加到转换为 JSON 的字典中? 如何反序列化上述 JSON 并返回静态类型为 Person 类型的实例化对象?同样,我不想手动映射属性。

这是在 C# 中使用 Json.NET 的方法:

var person = new Person("John", "Doe");
string json = JsonConvert.SerializeObject(person);
// "firstName":"John","lastName":"Doe"

Person deserializedPerson = JsonConvert.DeserializeObject<Person>(json);

【问题讨论】:

一般在 Swift/Objective-C 中,序列化意味着转换为NSData,因此对象可以存储为数据而不是一些纯文本格式。 这里不需要第 3 方 Json.NET,DataContractJsonSerializer 可以做到。 @Agent_L 我不想在这里深入讨论这场辩论,但 Newtonsoft.Json 比 DataContractJsonSerializer 更好用,功能更丰富。 如果我可以使用 C# 的 JSON.net 作为(真棒)示例,我会投票给你另一个! 【参考方案1】:

如WWDC2017@24:48 (Swift 4) 所示,我们将能够使用 Codable 协议。示例

public struct Person : Codable 
   public let firstName:String
   public let lastName:String
   public let location:Location

序列化

let payload: Data = try JSONEncoder().encode(person)

反序列化

let anotherPerson = try JSONDecoder().decode(Person.self, from: payload)

请注意,所有属性都必须符合 Codable 协议。

alternative 可以是JSONCodable,由Swagger's 代码生成器使用。

【讨论】:

是否可以将字典之类的属性作为结构的一部分并仍然使用 Codable?例如。变量项目:[字符串:任何]? .在那种情况下,可编码的工作吗?我觉得,它不会。 您知道Codable 基础结构是否可以用于其他编码。例如,协议缓冲区样式的二进制编码。 这很好,但你如何解码多个项目?如果我的有效负载包含 Person.self 数组?【参考方案2】:

您可以为此使用EVReflection。您可以使用如下代码:

var person:Person = Person(json:jsonString)

var jsonString:String = person.toJsonString()

有关更详细的示例代码,请参阅 GitHub 页面。您只需将 EVObject 设为数据对象的基类。不需要映射(只要json键与属性名相同)

更新: Swift 4 支持 Codable,这使得它几乎与 EVReflection 一样简单,但性能更好。如果您确实想使用像上面这样的简易承包商,那么您可以使用这个扩展:Stuff/Codable

【讨论】:

太好了,这就是我想要的。来自 C# 和 Ruby,解析 JSON 在 ios 开发中简直就是地狱。 EVReflection 非常有用。非常感谢【参考方案3】:

使用 Swift 4,您只需使您的类符合 CodableEncodableDecodable 协议),以便能够执行 JSON 序列化和反序列化。

import Foundation

class Person: Codable 
    let firstName: String
    let lastName: String

    init(firstName: String, lastName: String) 
        self.firstName = firstName
        self.lastName = lastName
    

用法 #1(将 Person 实例编码为 JSON 字符串):

let person = Person(firstName: "John", lastName: "Doe")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // if necessary
let data = try! encoder.encode(person)
let jsonString = String(data: data, encoding: .utf8)!
print(jsonString)

/*
 prints:
 
   "firstName" : "John",
   "lastName" : "Doe"
 
 */

用法 #2(将 JSON 字符串解码为 Person 实例):

let jsonString = """

  "firstName" : "John",
  "lastName" : "Doe"

"""

let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
dump(person)

/*
 prints:
 ▿ __lldb_expr_609.Person #0
   - firstName: "John"
   - lastName: "Doe"
 */

【讨论】:

【参考方案4】:

有一个名为 NSJSONSerialization 的 Foundation 类可以与 JSON 进行转换。

从 JSON 转换为对象的方法如下所示:

let jsonObject = NSJSONSerialization.JSONObjectWithData(data, 
    options: NSJSONReadingOptions.MutableContainers, 
    error: &error) as NSDictionary

请注意,此方法的第一个参数是 JSON 数据,但不是作为字符串对象,而是作为 NSData 对象(无论如何,这通常是您获取 JSON 数据的方式)。

您很可能希望您的类有一个工厂方法,该方法将 JSON 数据作为参数,使用此方法并返回您的类的初始化对象。

要反转此过程并从对象创建 JSON 数据,您需要使用 dataWithJSONObject,在其中您将传递一个可以转换为 JSON 的对象并返回一个 NSData?。同样,您可能希望创建一个不需要参数的辅助方法作为类的实例方法。


据我所知,处理此问题的最简单方法是创建一种将对象属性映射到字典并传递该字典以将对象转换为 JSON 数据的方法。然后在将您的 JSON 数据转换为对象时,期望返回一个字典并反转映射过程。不过可能有更简单的方法。

【讨论】:

我知道NSJSONSerialization 并使用字典,但这具体不是我正在寻找的。手动执行所有这些操作是编写样板代码的一种容易出错的方式,我在这里试图避免这种方式。 您可以使用NSCoding,但它仍然需要一些样板代码。见:nshipster.com/nscoding【参考方案5】:

您可以通过使用ObjectMapper 库来实现此目的。它可以让您更好地控制变量名称和值以及准备好的 JSON。添加此库后,扩展 Mappable 类并定义 mapping(map: Map) 函数。

例如

   class User: Mappable 
       var id: Int?
       var name: String?

       required init?(_ map: Map) 

       

       // Mapping code
       func mapping(map: Map) 
          name    <- map["name"]
          id      <- map["id"]
       

    

像下面这样使用它

   let user = Mapper<User>().map(JSONString)

【讨论】:

所有这一切都是为 OP 试图避免的样板代码添加一个很好的语法:为每个属性编写一堆序列化步骤。【参考方案6】:

首先,像这样创建一个 Swift 对象

struct Person 
    var firstName: String?;
    var lastName: String?;
    init() 

    

之后,使用内置的 NSJSONSerialization 序列化您检索到的 JSON 数据,并将值解析到 Person 对象。

var person = Person();
var error: NSError?;
var response: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(), error: &error);

if let personDictionary = response as? NSDictionary 
    person.firstName = personDictionary["firstName"] as? String;
    person.lastName = personDictionary["lastName"] as? String;

更新:

也请看看那些库

Swift-JsonSerialiser

ROJSONParser

【讨论】:

这种方法有效,但恐怕不能回答我的问题。我正在寻找一种不需要需要手动设置每个属性的解决方案。 @MariusSchulz 我认为答案“没有这样的第一方”也是一个重要的答案,尽管在这里可能更清楚。【参考方案7】:

查看 NSKeyValueCoding.h,特别是 setValuesForKeysWithDictionary。将 json 反序列化为 NSDictionary 后,您就可以使用该字典实例创建和初始化对象,无需手动设置对象的值。这将使您了解反序列化如何与 json 一起工作,但您很快就会发现您需要对反序列化过程进行更多控制。这就是为什么我在NSObject 上实现了一个类别,它允许在json 反序列化期间使用字典完全控制NSObject 初始化,它基本上比setValuesForKeysWithDictionary 更进一步丰富了对象。我还有一个 json 反序列化器使用的协议,它允许被反序列化的对象控制某些方面,例如,如果反序列化对象的 NSArray,它将询问该对象存储在数组中的对象的类型名称是什么。

【讨论】:

以上是关于Swift 中对象的自动 JSON 序列化和反序列化的主要内容,如果未能解决你的问题,请参考以下文章

(JSON) 序列化和反序列化,这个是啥意思呀?

无法反序列化当前的JSON对象,为啥

JSON PHP中,Json字符串反序列化成对象/数组的方法

将 Objective-C 对象序列化和反序列化为 JSON

Swift语言精要 - 序列化和反序列化

C#中使用Newtonsoft.Json序列化和反序列化自定义类对象