将[String:Any]序列化为JSON

时间:2016-02-06 21:14:44

标签: json linux swift

我在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(", "))}"
    }
}

5 个答案:

答案 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: ", "))}"
  }
}