我在Linux上试用Swift。
我有带字符串键的字典形式的数据,以及任何类型的值,我试图将这些数据序列化为JSON格式的字符串。
抱怨, NSJSONSerialization.dataWithJSONObject
没有用
Argument type '[String : Any]' does not conform to expected type 'AnyObject'
let dict : [String : Any] = [
"string" : "Hello",
"int" : 1,
"double" : 3.14,
"array str" : ["a", "b", "c"],
"array double" : [1.0, 2.0, 3.0],
"array int" : [1, 2, 3]
]
我知道它必须是可行的,因为字典的字符串表示已经几乎是正确的格式:
print("\(dict)")
["array int": [1, 2, 3], "int": 1, "array double": [1.0, 2.0, 3.0], "string": "Hello", "double": 3.1400000000000001, "array str": ["a", "b", "c"]]
在建议将dict声明为[String:AnyObject]
之前,字典是从动态数据填充的,而不是声明为文字。
如果可能,我想限制服务器上可能无法使用的OSX或iOS特定库的使用。
修改:
以下是Enrico Granata提出的解决方案的实现:
protocol JSONSerializable {
func toJSON() -> String?
}
extension String : JSONSerializable {
func toJSON() -> String? {
return "\"\(self)\""
}
}
extension Int : JSONSerializable {
func toJSON() -> String? {
return "\(self)"
}
}
extension Double : JSONSerializable {
func toJSON() -> String? {
return "\(self)"
}
}
extension Array : JSONSerializable {
func toJSON() -> String? {
var out : [String] = []
for element in self {
if let json_element = element as? JSONSerializable, let string = json_element.toJSON() {
out.append(string)
} else {
return nil
}
}
return "[\(out.joinWithSeparator(", "))]"
}
}
extension Dictionary : JSONSerializable {
func toJSON() -> String? {
var out : [String] = []
for (k, v) in self {
if let json_element = v as? JSONSerializable, let string = json_element.toJSON() {
out.append("\"\(k)\": \(string)")
} else {
return nil
}
}
return "{\(out.joinWithSeparator(", "))}"
}
}
答案 0 :(得分:1)
我确信在我说出这个问题时会有一些错误,因为这是一个棘手的话题,但请耐心等待。
NSJSONSerialization是一个Foundation API,而不是Swift标准库。因此,它来自Darwin上的Objective-C,尽管它可能已在Linux上重新实现。原始API也是一个Objective-C API,它在OS X上被桥接到Swift。
根据引用类型(AnyObject)进行桥接。在Objective-C中,这非常好,因为所有Objective-C对象都是引用类型,可以作为一个公共类型(id,或Swift,AnyObject)引用。在纯Swift方面,有些类型不是引用类型(Array,Dictionary,Int,String,...)。正如您似乎已经知道的那样,所有这些都可以被描述为确认名为Any的魔术协议。 Any实际上只是协议<>的类型别名,但是它周围存在编译器魔法。
为了兼容其Darwin版本的版本,Foundation的NSJSONSerialization喜欢用AnyObject来谈谈,而不是Any。因此,存在它不能序列化的类型。
我可以看到一些途径:
a)您可以利用Bridgeable协议尝试从[String:Any]转到[String:X其中X:Bridgeable](不是真正的语法)
如果您查看https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSArray.swift,您会看到该基金会添加:
extension Array : Bridgeable {
public func bridge() -> NSArray { return _nsObject }
}
您应该能够使用它来遍历您的对象图并获得纯粹的Swift对象的NS *版本,然后可以传递给NSJSONSerialization。你的里程当然可能会有所不同,因为并非一切都是无损的桥梁。
b)你可以编写自己的JSON序列化程序来处理本机Swift类型
如果沿着这条路走下去,你就不再关心Any或AnyObject等等。很简单,你做的事情就像
protocol JSONSerializable { func toJSON() -> String? }
然后是一个数组,
extension Array : JSONSerializable { func toJSON() -> String? {
out = "["
for element in self { if let json_element = element as? JSONSerializable {
if let string = json_element.toJSON() { out = out + string } else { return nil }
}
out = out + "]"
return out
}
}
需要注意的是,这段代码(a)不是一个完整的实现,(b)也不一定是最聪明最有效的方式。考虑一下你需要做的事情的暗示。如果你沿着这条路走下去,就会失去一些代码重用,并且不得不担心JSON语法有什么怪癖,但另一方面,你可以完全控制序列化过程,你可以不再担心Any了。
答案 1 :(得分:1)
使用Swift 3.0 Preview 3,您可以这样做:
JSONSerialization.data( withJSONObject: dict.bridge() )
答案 2 :(得分:0)
即使你不喜欢听,[String: AnyObject]
也是要走的路。我认为没有任何不利之处。它很好地涵盖了可序列化为JSON的所有数据类型。它可以很容易地动态填充。
在XCode Playground中测试:
let dict : [String : AnyObject] = [
"string" : "Hello",
"int" : 1,
"double" : 3.14,
"array str" : ["a", "b", "c"],
"array double" : [1.0, 2.0, 3.0],
"array int" : [1, 2, 3]
]
let jsonData = try! NSJSONSerialization.dataWithJSONObject(dict, options: [.PrettyPrinted])
let x = String(data: jsonData, encoding: NSUTF8StringEncoding)!
print(x)
输出:
{
"array double" : [
1,
2,
3
],
"array str" : [
"a",
"b",
"c"
],
"int" : 1,
"array int" : [
1,
2,
3
],
"string" : "Hello",
"double" : 3.14
}
答案 3 :(得分:0)
您可以定义一个typealias并使用它:
#if os(Linux)
typealias JsonDict = [String: Any]
#else
typealias JsonDict = [String: AnyObject]
#endif
let dict : JsonDict = [
"string" : "Hello",
"int" : 1,
"double" : 3.14,
"array str" : ["a", "b", "c"],
"array double" : [1.0, 2.0, 3.0],
"array int" : [1, 2, 3]
]
答案 4 :(得分:0)
如果您希望能够在Linux上处理此问题(在类型转换中更加严格),您需要更接近以下内容。请注意任何转换功能。我在托管的linux服务器上测试了这个。此外,我在这里添加了对SwiftyJSON词典的强制转换的支持。 SwiftyJSON包需要在Linux上排除一些工作(特别是从初始化程序中删除NSErrorPointer),并且要使用它的rawDictionary需要公开。如果您只关心Serializer,请随意忽略该部分。
SwiftyJSON扩展
import SwiftyJSON
extension JSON {
public func hackyRawString() -> String? {
switch self.type {
case .dictionary:
let string = self.rawDictionary.toJSON()
return string
default:
return self.rawString()
}
}
}
Base JSONSerializer:
public protocol JSONSerializable {
func toJSON() -> String?
func anyConv(_ body: Any) -> String?
}
extension JSONSerializable {
public func anyConv(_ element: Any) -> String? {
if let json_element = element as? JSONSerializable, let string = json_element.toJSON() {
return string
} else if let json_element = Int("\(element)") as? JSONSerializable, let string = json_element.toJSON() {
return string
} else if let json_element = Bool("\(element)") as? JSONSerializable, let string = json_element.toJSON() {
return string
} else if let json_element = Double("\(element)") as? JSONSerializable, let string = json_element.toJSON() {
return string
} else {
return nil
}
}
}
extension String : JSONSerializable {
public func toJSON() -> String? {
return "\"\(self)\""
}
}
extension Int : JSONSerializable {
public func toJSON() -> String? {
return "\(self)"
}
}
extension Double : JSONSerializable {
public func toJSON() -> String? {
return "\(self)"
}
}
extension Bool : JSONSerializable {
public func toJSON() -> String? {
return "\(self)"
}
}
extension Array : JSONSerializable {
public func toJSON() -> String? {
var out : [String] = []
for element in self {
if let string = anyConv(element) {
out.append(string)
} else {
out.append("null")
}
}
return "[\(out.joined(separator: ", "))]"
}
}
extension Dictionary : JSONSerializable {
public func toJSON() -> String? {
var out : [String] = []
for (k, v) in self {
if let string = anyConv(v) {
out.append("\"\(k)\": \(string)")
} else {
out.append("\"\(k)\": null")
}
}
return "{\(out.joined(separator: ", "))}"
}
}