Home Ask Login Register

Developers Planet

Your answer is one click away!

Mzzl February 2016

Serialize [String:Any] to JSON

I'm trying out Swift on Linux.

I have data in the form of dictionaries with String keys, and values of Any type, and am trying to serialize these to a String in JSON format.

NSJSONSerialization.dataWithJSONObject did not work, it complains 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]
]

I know it must be doable, as the string representation of the dictionary is already almost in the right format:

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

Before you suggest declaring dict as a [String:AnyObject], the dictionary is filled from dynamic data, not declared as a literal.

If possible, I'd like to limit the use of OSX or iOS specific libraries that might not be available on the server.

Edit:

Here's an implementation of the solution proposed by 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
             

Answers


Enrico Granata February 2016

I am sure there will be a few errors in the way I phrase this, since it's a tricky topic, but bear with me.

NSJSONSerialization is a Foundation API, not a Swift standard library one. As such, it comes from Objective-C on Darwin even though it has probably been reimplemented on Linux. The original API, again, is an Objective-C API which on OS X gets bridged over to Swift.

The bridging happens in terms of reference types (AnyObject). In Objective-C, that's quite good enough since all things that are Objective-C objects are reference types that can be referenced as one common type (id, or in Swift, AnyObject). On the pure Swift side, there are types that are not reference types (Array, Dictionary, Int, String, ...). All of those can be described, as you seem to know already, as confirming to a magic protocol named Any. Any is really just a type alias for protocol<>, but compiler magic exists around it.

For compatibility with its stock Darwin version, Foundation's NSJSONSerialization likes to talk in terms of AnyObject, not in terms of Any. As such, there exist types that it can't serialize.

I can see a few avenues:

a) you could exploit the Bridgeable protocol to try and go from [String: Any] to [String: X where X: Bridgeable] (not real syntax)

If you look at https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSArray.swift, you'll see that Foundation adds:

extension Array : Bridgeable {
    public func bridge() -> NSArray { return _nsObject }
}

You should be able to use this to traverse your object graph and obtain an NS* version of your pure Swift objects, that would then be OK to pass to NSJSONSerialization. Your mileage may of course vary since not everything is necessarily losslessly bridgeable.

b) you could write your own JSON ser


Codo February 2016

Even if you don't like to hear it, [String: AnyObject] is the way to go. And I don't see any disadvantage with it. It nicely covers all the data types that are serializable to JSON. And it can easily be filled dynamically.

Tested in 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)

Output:

{
  "array double" : [
    1,
    2,
    3
  ],
  "array str" : [
    "a",
    "b",
    "c"
  ],
  "int" : 1,
  "array int" : [
    1,
    2,
    3
  ],
  "string" : "Hello",
  "double" : 3.14
}

Post Status

Asked in February 2016
Viewed 2,747 times
Voted 12
Answered 2 times

Search




Leave an answer


Quote of the day: live life