将[String:Any]序列化为JSON

4

我正在尝试在Linux上使用Swift。

我有一些数据,它们是由String键和任意类型的值组成的字典,并且我正在尝试将它们序列化为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(", "))}"
    }
}

1
抱歉,为什么您使用动态数据就不能使用从字符串到对象的字典映射呢? - Tommy
由于 error: value of type 'Double' does not conform to expected dictionary value type 'AnyObject' - Mzzl
1
不幸的是,我认为你正在处理的是Swift中丑陋的Objective-C边缘之一 - 它对包含引用类型的集合没有问题,但Objective-C不支持引用类型(在语义上),而NSJSONSerialization可能仍然是Objective-C代码。我不想建议你切换到AnyObject并将值包装为NSNumber,因为这不是你在Swift中应该采取的丑陋解决方案。所以希望有人有比那更好的解决方案。我会打开一个playground并快速尝试一下。 - Tommy
5个回答

1
我确定在表达这个话题时会有一些错误,因为这是一个棘手的话题,但请耐心听我说。
NSJSONSerialization是一个Foundation API,而不是Swift标准库API。因此,它来自Objective-C on Darwin,尽管它可能已经在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。因此,存在它无法序列化的类型。
我能看到几条途径:

您可以利用可桥接协议尝试从 [String: Any] 转换为 [String: X where X: Bridgeable](非真实语法)。

如果您查看 https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSArray.swift,您会发现 Foundation 添加了:

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。

由于我不确定a)的含义,所以我尝试了b),但是我仍然困惑于什么是或不是Any类型的问题:let a = [1,[2, 3, 4],5]; if let val = a [1] as?[Any] ....似乎Array <Int>不是Array <Any>。 - Mzzl
看我的修改——方法b)与你尝试的类型转换无关。 - Enrico Granata
我没有Objective-C的背景,直接学习了Swift。我接受了你的答案,因为我觉得它很有启发性。最终我自己编写了一个序列化程序,采用了非常不同的方法,需要考虑类型问题。我也会尝试使用你的方法,因为它看起来更加优雅和简短。 - Mzzl

1
使用Swift 3.0预览版3,您只需执行以下操作:

JSONSerialization.data( withJSONObject: dict.bridge() )

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
}

如果您导入Foundation,字典文字会将值包装在任何Object兼容类型中,例如NSNumber。这似乎仅在编译时起作用,并且仅适用于osx / ios,而不适用于Linux实现。 - Mzzl

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]
]

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()
    }
  }
}

基础的JSON序列化器:

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

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