Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

327
Views
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
            }
        }
        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(", "))}"
    }
}
over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

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 serializer that can deal with native Swift types

If you go down this path, you stop caring about Any or AnyObject or whatnot. Quite simply, you'd do something like

protocol JSONSerializable { func toJSON() -> String? }

and then for an Array,

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

To be noted, that this code (a) is not a complete implementation as-is, (b) is also not necessarily the smartest most efficient way. Consider it a hint in the direction of what you would need to do. If you go down this road, you lose some code reuse, and have to worry about whatever quirks the JSON syntax has, but on the other hand, you get pretty much complete control over the serialization process, and you can stop worrying about Any.

over 4 years ago · Santiago Trujillo Report

0

With Swift 3.0 Preview 3, you can just do:

JSONSerialization.data( withJSONObject: dict.bridge() )
over 4 years ago · Santiago Trujillo Report

0

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
}
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!