There are 3 string variables
public var userLoginId : String?
public var searchString : String?
public var tableName : String?
I have a dictionary:
let dict = ["userLoginId" : userLoginId, "searchString" : searchString,"tableName" : tableName]
Now I serialize it by JSON:
let data = try! JSONSerialization.data(withJSONObject: dict, options :[])
let jsonstring = String(data:data, encoding:.utf8)!
In jsonstring, every time I get an unordered JSON like below:
"{\"tableName\":\"null\",\"userLoginId\":\"Dilip\",\"searchString\":\"Tata\"}"
How can I get in the same format in which I assigned value to dictionary?
Dictionaries are unordered
Since JSON is a String you can have an ordered JSON.
You can use this library to parse the JSON in the original order. The usage is simple:
try JSON.parse(string: jsonString)
From iOS 11 and macOS 10.13 there is a new option for JSONSerialization for sorting keys that you can use:
JSONSerialization.data(withJSONObject: json, options: [.sortedKeys])
Once you get over everyone telling you you can't, or its wrong, or you can't rely on a JSON object's order because of the specification…etc. You can just go ahead and do the impossible as follows:
Add a dependency to swift-collections in your Package.swift so we have access to OrderedDictionary.
Go grab a copy of JSONSerialization+Parser.swift from swift-corelibs-foundation and put it somehwere in your project.
Using curl:
curl -L -o "${YOUR_PROJECT_SOURCE_DIR}/JSONSerialization+Parser.swift" 'https://raw.githubusercontent.com/apple/swift-corelibs-foundation/c284a2390b9050386758e36e4c2a2b648493efd9/Sources/Foundation/JSONSerialization%2BParser.swift'
Or download manually from GitHub (Note: my patch was created against commit c284a23) : Permalink to JSONSerialization+Parser.swift (commit c284a23)
Apply the following changes to JSONSerialization+Parser.swift
@@ -9,7 +9,8 @@
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
-
+import OrderedCollections
+import Foundation
internal struct JSONParser {
var reader: DocumentReader
@@ -142,7 +143,7 @@
// MARK: - Object parsing -
- mutating func parseObject() throws -> [String: JSONValue] {
+ mutating func parseObject() throws -> OrderedDictionary<String, JSONValue> {
precondition(self.reader.read() == ._openbrace)
guard self.depth < 512 else {
throw JSONError.tooManyNestedArraysOrDictionaries(characterIndex: self.reader.readerIndex - 1)
@@ -162,7 +163,7 @@
break
}
- var object = [String: JSONValue]()
+ var object = OrderedDictionary<String, JSONValue>()
object.reserveCapacity(20)
while true {
@@ -663,3 +664,29 @@
case singleFragmentFoundButNotAllowed
case invalidUTF8Sequence(Data, characterIndex: Int)
}
+
+enum JSONValue: Equatable {
+ case null
+ case array([JSONValue])
+ case object(OrderedDictionary<String, JSONValue>)
+ case bool(Bool)
+ case string(String)
+ case number(String)
+
+ func unwrap() throws -> Any {
+ switch self {
+ case .null: return NSNull()
+ case let .array(values): return try values.map { try $0.unwrap() }
+ case let .object(object): return try object.mapValues { try $0.unwrap() }
+ case let .bool(bool): return NSNumber(value: bool)
+ case let .string(string): return string
+ case let .number(string):
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .decimal
+ guard let number = formatter.number(from: string) else {
+ throw JSONError.numberIsNotRepresentableInSwift(parsed: string)
+ }
+ return number
+ }
+ }
+}
You can then use it like this:
import Foundation
import OrderedCollections
@main
public enum swift_json_ordered_dictionary {
public static func main() {
let jsonStr = """
{
"name": "John Doe",
"age": 30,
"orderMatters": "Sometimes order matters even when the world is telling you you're wrong",
"inOrder": "Look at me ma, I've got myself in order",
"ducksInARow": "Having all your ducks in a row",
"favorites": {
"color": "Rainbow",
"food": "Pizza",
"animal": "Unicorn",
"activity": "Napping"
},
"programmingLanguages": ["Swift", "Python", "JavaScript", "Gibberish"],
"job": "Developer",
"quote": "Debugging is like being the detective in a crime movie where you are also the murderer",
"nestedObjects": {
"nested1": {
"message": "Order matters, even in nested objects!"
},
"nested2": {
"message": "Embrace the chaos, but keep things in order."
},
"nested3": {
"message": "Defying conventions, because order is a beautiful thing."
}
}
}
"""
do {
var parser = JSONParser(bytes: Array(jsonStr.utf8))
let jsonValue = try parser.parse()
let x = try jsonValue.unwrap()
if let dict = x as? OrderedDictionary<String, Any> {
print(dict.prettyPrint())
}
} catch { print("\(error)") }
}
}
Note: the .prettyPrint() function is just an extension I added to OrderedDictionary to give a nice output like this:
{
"name": "John Doe",
"age": 30,
"orderMatters": "Sometimes order matters even when the world is telling you you're wrong",
"inOrder": "Look at me ma, I've got myself in order",
"ducksInARow": "Having all your ducks in a row",
"favorites": {
"color": "Rainbow",
"food": "Pizza",
"animal": "Unicorn",
"activity": "Napping"
},
"programmingLanguages": [
"Swift",
"Python",
"JavaScript",
"Gibberish",
],
"job": "Developer",
"quote": "Debugging is like being the detective in a crime movie where you are also the murderer",
"nestedObjects": {
"nested1": {
"message": "Order matters, even in nested objects!"
},
"nested2": {
"message": "Embrace the chaos, but keep things in order."
},
"nested3": {
"message": "Defying conventions, because order is a beautiful thing."
}
}
}
And as you can see, order was maintained with very minimal changes to JSONSerialization+Parser.swift.
Here is a little helper to apply the changes above to JSONSerialization+Parser.swift in a single command:
patch "${YOUR_PROJECT_SOURCE_DIR}/JSONSerialization+Parser.swift" < <(cat <<'EOF'
@@ -9,7 +9,8 @@
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
-
+import OrderedCollections
+import Foundation
internal struct JSONParser {
var reader: DocumentReader
@@ -142,7 +143,7 @@
// MARK: - Object parsing -
- mutating func parseObject() throws -> [String: JSONValue] {
+ mutating func parseObject() throws -> OrderedDictionary<String, JSONValue> {
precondition(self.reader.read() == ._openbrace)
guard self.depth < 512 else {
throw JSONError.tooManyNestedArraysOrDictionaries(characterIndex: self.reader.readerIndex - 1)
@@ -162,7 +163,7 @@
break
}
- var object = [String: JSONValue]()
+ var object = OrderedDictionary<String, JSONValue>()
object.reserveCapacity(20)
while true {
@@ -663,3 +664,29 @@
case singleFragmentFoundButNotAllowed
case invalidUTF8Sequence(Data, characterIndex: Int)
}
+
+enum JSONValue: Equatable {
+ case null
+ case array([JSONValue])
+ case object(OrderedDictionary<String, JSONValue>)
+ case bool(Bool)
+ case string(String)
+ case number(String)
+
+ func unwrap() throws -> Any {
+ switch self {
+ case .null: return NSNull()
+ case let .array(values): return try values.map { try $0.unwrap() }
+ case let .object(object): return try object.mapValues { try $0.unwrap() }
+ case let .bool(bool): return NSNumber(value: bool)
+ case let .string(string): return string
+ case let .number(string):
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .decimal
+ guard let number = formatter.number(from: string) else {
+ throw JSONError.numberIsNotRepresentableInSwift(parsed: string)
+ }
+ return number
+ }
+ }
+}
EOF
)
For easier testing I've added the ...PrettyPrint funcs (neither fine nor fast, but working :-)):
fileprivate func JsonPrettyPrint(_ value: Any, offset: String = "") -> String {
var result: String = ""
switch value {
case is NSNull: result += ""; break
case let asArray as [Any]: result += asArray.jsonPrettyPrint(); break
case let asOrderedDict as OrderedDictionary<String, Any>: result += asOrderedDict.jsonPrettyPrint(offset: offset); break
case let asNSNumber as NSNumber: result += asNSNumber.stringValue; break
case let asString as String: result += "\"\(asString)\""; break
default: result += "???"; break
}
return result
}
extension Array where Element == Any {
func jsonPrettyPrint(offset: String = "") -> String {
var result: String = ""
for value in self {
if !result.isEmpty { result += ", " }
result += JsonPrettyPrint(value, offset: offset + "")
}
return "[" + result + "]"
}
}
extension OrderedDictionary where Key == String, Value == Any {
func jsonPrettyPrint(offset: String = "") -> String {
var result: String = ""
for (key, value) in self {
if !result.isEmpty { result += ",\n" }
result += offset + "\t" + "\"\(key)\": " + JsonPrettyPrint(value, offset: offset + "\t")
}
return "{\n" + result + "\n\(offset)}"
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With