如何将Swift对象序列化或转换为JSON?

133

以下是这个类:

class User: NSManagedObject {
  @NSManaged var id: Int
  @NSManaged var name: String
}

需要转换为

{
    "id" : 98,
    "name" : "Jon Doe"
}

我曾尝试手动将对象传递给一个函数,该函数将变量设置为字典并返回字典。但我想有更好的方法来完成这个任务。


1
你最好是先运行它并将其保存到数组和字典中,然后再进行转换。 - Schemetrical
请参考此链接 - https://github.com/dankogai/swift-json - Uttam Sinha
嗯,你需要遍历对象本身,获取所有的值,并手动将其添加到一个字典中,然后重复这个过程。 - Schemetrical
@Schemetrical 我实际上尝试过那个。但是对于大对象,编译时间会显著增加。 - Penkey Suresh
@redo1135,你的代码在做什么?编译时间不应该显著增加,我不知道你做了什么导致编译时间增加。 - Schemetrical
显示剩余2条评论
9个回答

201

在 Swift 4 中,你可以继承自 Codable 类型。

struct Dog: Codable {
    var name: String
    var owner: String
}

// Encode
let dog = Dog(name: "Rex", owner: "Etgar")

let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(dog)
let json = String(data: jsonData, encoding: String.Encoding.utf8)

// Decode
let jsonDecoder = JSONDecoder()
let secondDog = try jsonDecoder.decode(Dog.self, from: jsonData)

63
编码类型应该是 .utf8 而不是 .utf16。 - Chan Jing Hong
4
这取决于你想要编码的内容。 - Etgar
1
针对这个特定的例子,.utf16 能用吗?我尝试了但不起作用。 - Chan Jing Hong
1
@ChanJingHong 奇怪,我三个月前试过它并且它可以工作。我想我应该在解码方法中也将编码类型作为参数放入。我会检查一下的。 - Etgar
3
问题特别关于编码 class(我需要的)而不是 struct - gondo
显示剩余5条评论

54
Swift 4 (Foundation) 现已原生支持两种方式,即将 JSON 字符串转换为对象和将对象转换为 JSON 字符串。请参阅 Apple 的文档 JSONDecoder()JSONEncoder()
将 JSON 字符串转换为对象。
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let myStruct = try! decoder.decode(myStruct.self, from: jsonData)

将Swift对象转换为JSON字符串

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(myStruct)
print(String(data: data, encoding: .utf8)!)

您可以在此处找到所有细节和示例 使用 Swift 4 的 JSON 解析终极指南


28

更新: Swift 4中引入的Codable协议对于大多数JSON解析案例应该已经足够了。以下答案适用于那些被困在早期Swift版本和需要支持旧代码的人。

EVReflection:

  • 这是基于反射原理工作的。它需要较少的代码,并且还支持NSDictionaryNSCodingPrintableHashableEquatable

示例:

    class User: EVObject { # extend EVObject method for the class
       var id: Int = 0
       var name: String = ""
       var friends: [User]? = []
    }

    # use like below
    let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
    let user = User(json: json)

ObjectMapper :

  • 使用ObjectMapper是另一种方式。这种方法可以提供更多的控制,但需要编写更多的代码。

示例:

    class User: Mappable { # extend Mappable method for the class
       var id: Int?
       var name: String?

       required init?(_ map: Map) {

       }

       func mapping(map: Map) { # write mapping code
          name    <- map["name"]
          id      <- map["id"]
       }

    }

    # use like below
    let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
    let user = Mapper<User>().map(json)

1
它是否也支持映射图像? - Charlie
2
@Charlie,抱歉我不确定那是否可能。 - Penkey Suresh
2
@ Suresh:可以通过编写自定义转换来实现,如示例所示。我将图像转换为字符串,然后添加到Json对象中。这对于在watch os上工作非常有帮助。 - Charlie
2
你好,你知道如何手动初始化一个 Mappable 类并设置属性,然后将对象转换为 jsonString 吗? - VAAA
Swift 4 / iOS 11 中的 Codeable 协议会产生什么影响?我们能否使用它将 NSManagedObject 转换为 JSON? - user1828845
如果您正在使用ObjectMapper,您可以使用BetterMappable来减少大量的样板代码,该库是基于PropertyWrappers编写的。您需要使用Swift 5.1才能使用它。 - Srikanth

15

我尝试开发了一个较小的解决方案,不需要使用继承。但是它没有经过充分测试,目前看起来相当丑陋。

https://github.com/peheje/JsonSerializerSwift

您可以将其传入playground进行测试。例如以下类结构:

//Test nonsense data
class Nutrient {
    var name = "VitaminD"
    var amountUg = 4.2

    var intArray = [1, 5, 9]
    var stringArray = ["nutrients", "are", "important"]
}

class Fruit {
    var name: String = "Apple"
    var color: String? = nil
    var weight: Double = 2.1
    var diameter: Float = 4.3
    var radius: Double? = nil
    var isDelicious: Bool = true
    var isRound: Bool? = nil
    var nullString: String? = nil
    var date = NSDate()

    var optionalIntArray: Array<Int?> = [1, 5, 3, 4, nil, 6]
    var doubleArray: Array<Double?> = [nil, 2.2, 3.3, 4.4]
    var stringArray: Array<String> = ["one", "two", "three", "four"]
    var optionalArray: Array<Int> = [2, 4, 1]

    var nutrient = Nutrient()
}

var fruit = Fruit()
var json = JSONSerializer.toJson(fruit)

print(json)

打印

{"name": "Apple", "color": null, "weight": 2.1, "diameter": 4.3, "radius": null, "isDelicious": true, "isRound": null, "nullString": null, "date": "2015-06-19 22:39:20 +0000", "optionalIntArray": [1, 5, 3, 4, null, 6], "doubleArray": [null, 2.2, 3.3, 4.4], "stringArray": ["one", "two", "three", "four"], "optionalArray": [2, 4, 1], "nutrient": {"name": "VitaminD", "amountUg": 4.2, "intArray": [1, 5, 9], "stringArray": ["nutrients", "are", "important"]}}

请问您能提供它的Swift 2.3版本吗? - H Raval
看起来很棒,但它不能与SPM一起使用... - matteoh

9
这不是一个完美/自动化的解决方案,但我相信这是最符合惯用语和本地方式的方法。这样,您就不需要任何库或类似的东西。
创建一个协议,例如:
/// A generic protocol for creating objects which can be converted to JSON
protocol JSONSerializable {
    private var dict: [String: Any] { get }
}

extension JSONSerializable {
    /// Converts a JSONSerializable conforming class to a JSON object.
    func json() rethrows -> Data {
        try JSONSerialization.data(withJSONObject: self.dict, options: nil)
    }
}

然后在您的类中实现它,例如:
class User: JSONSerializable {
    var id: Int
    var name: String

    var dict { return ["id": self.id, "name": self.name]  }
}

现在:

let user = User(...)
let json = user.json()

注意:如果你想将 json 转换为字符串,非常简单:使用以下代码即可:String(data: json, encoding: .utf8)


8

以上一些答案完全没问题,但我增加了一个扩展,使其更易读和可用。

extension Encodable {
    var convertToString: String? {
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        do {
            let jsonData = try jsonEncoder.encode(self)
            return String(data: jsonData, encoding: .utf8)
        } catch {
            return nil
        }
    }
}

struct User: Codable {
     var id: Int
     var name: String
}

let user = User(id: 1, name: "name")
print(user.convertToString!)

//这将打印如下:

{
  "id" : 1,
  "name" : "name"
}

4

2021 | SWIFT 5.1 | 结果解决方案

数据样本:

struct ConfigCreds: Codable { // Codable is important!
    // some params
}

解决方案使用示例:
var configCreds = ConfigCreds()
var jsonStr: String = ""

// Object -> JSON
configCreds
   .asJson()
   .onSuccess { jsonStr = $0 }
   .onFailure { _ in // any failure code }

// JSON -> object of type "ConfigCreds"
someJsonString
    .decodeFromJson(type: ConfigCreds.self)
    .onSuccess { configCreds = $0 }
    .onFailure { _ in // any failure code }

后端代码:
@available(macOS 10.15, *)
public extension Encodable {
    func asJson() -> Result<String, Error>{
        JSONEncoder()
            .try(self)
            .flatMap{ $0.asString() }
    }
}

public extension String {
    func decodeFromJson<T>(type: T.Type) -> Result<T, Error> where T: Decodable {
        self.asData()
            .flatMap { JSONDecoder().try(type, from: $0) }
    }
}

///////////////////////////////
/// HELPERS
//////////////////////////////

@available(macOS 10.15, *)
fileprivate extension JSONEncoder {
    func `try`<T : Encodable>(_ value: T) -> Result<Output, Error> {
        do {
            return .success(try self.encode(value))
        } catch {
            return .failure(error)
        }
    }
}

fileprivate extension JSONDecoder {
    func `try`<T: Decodable>(_ t: T.Type, from data: Data) -> Result<T,Error> {
        do {
            return .success(try self.decode(t, from: data))
        } catch {
            return .failure(error)
        }
    }
}

fileprivate extension String {
    func asData() -> Result<Data, Error> {
        if let data = self.data(using: .utf8) {
            return .success(data)
        } else {
            return .failure(WTF("can't convert string to data: \(self)"))
        }
    }
}

fileprivate extension Data {
    func asString() -> Result<String, Error> {
        if let str = String(data: self, encoding: .utf8) {
            return .success(str)
        } else {
            return .failure(WTF("can't convert Data to string"))
        }
    }
}

fileprivate func WTF(_ msg: String, code: Int = 0) -> Error {
    NSError(code: code, message: msg)
}

internal extension NSError {
    convenience init(code: Int, message: String) {
        let userInfo: [String: String] = [NSLocalizedDescriptionKey:message]
        self.init(domain: "FTW", code: code, userInfo: userInfo)
    }
}

不确定如何使用这个。IDE报错显示“类型'JSONEncoder.Output'的值没有成员'asString'”和“类型'String'的值没有成员'asData'”。 - DevinM
@DevinM 顺便说一下,我已经在这里修复了代码。 - Andrew_STOP_RU_WAR_IN_UA

4
struct User:Codable{
 var id:String?
 var name:String?
 init(_ id:String,_ name:String){
   self.id  = id
   self.name = name
 }
}

现在只需像这样创建您的对象:

let user = User("1","pawan")


该内容与编程有关。
do{
      let userJson =  try JSONEncoder().encode(parentMessage) 
            
    }catch{
         fatalError("Unable To Convert in Json")      
    }

然后再从 json 转换为对象。
let jsonDecoder = JSONDecoder()
do{
   let convertedUser = try jsonDecoder.decode(User.self, from: userJson.data(using: .utf8)!)
 }catch{
   
 }

2

不确定是否存在库/框架,但如果您想自动完成并避免手动劳动 :-) ,请使用MirrorType ...

class U {

  var id: Int
  var name: String

  init(id: Int, name: String) {
    self.id = id
    self.name = name
  }

}

extension U {

  func JSONDictionary() -> Dictionary<String, Any> {
    var dict = Dictionary<String, Any>()

    let mirror = reflect(self)

    var i: Int
    for i = 0 ; i < mirror.count ; i++ {
      let (childName, childMirror) = mirror[i]

      // Just an example how to check type
      if childMirror.valueType is String.Type {
        dict[childName] = childMirror.value
      } else if childMirror.valueType is Int.Type {
        // Convert to NSNumber for example
        dict[childName] = childMirror.value
      }
    }

    return dict
  }

}

以一个简单的例子来说,它缺乏适当的转换支持,也没有递归... 这只是一个MirrorType演示...

P.S. 这里使用的是U,但你将会增强NSManagedObject,然后就可以转换所有的NSManagedObject子类了。不需要在所有子类/托管对象中实现这个。


嗨,我正在尝试这种方法,但每次都得到0作为计数。你能更具体地告诉我该怎么做吗?我认为@NSManaged会引起问题。我该如何增强NSManagedObject? - Penkey Suresh
没有尝试过使用@NSManaged。也许它是导致问题的原因。在这种情况下,我会用Objective-C编写它,然后从Swift中调用它。 - zrzka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接