Empty dictionary are encoded to empty JSON arrays if key is UUID - json

I'm trying to encode/decode JSON with Swift's JSONEncoder/Decoder. My JSON contains a dictionary with UUID as Key. If this dictionary is empty, Swift fails with
Expected to decode Array<Any> but found a dictionary instead.
While analyzing I noticed, that Swift creates different repressions of the empty dictionary depending on the key's type. The following minimum example illustrates the problem quite well:
import Foundation
typealias Key = UUID // or String
struct Foo: Codable {
let data: [Key: String]
}
let foo = Foo(data: [:])
let encodedData = try JSONEncoder().encode(foo)
let foo2 = try JSONDecoder().decode(Foo.self, from: encodedData)
print(String(decoding: encodedData, as: UTF8.self))
When using UUID as key's type you get:
{"data":[]}
and when using String as key's type you get:
{"data":{}}
I'd expect {"data":{}} in all cases. What I'm doing wrong here?

Related

Convert NSObject to JSON string

I have a class that conforms to NSObject and has fields like NSNumber and NSMutableArray, so cannot really use Codable here.
So the class is like this:
class Custom: NSObject {
var users: NSMutableArray?
var type: NSNumber?
.. and many more fields
}
Now i have an object of this class and want to get JSON string from that object :
I have tried following :
let json = try? JSONSerialization.data(withJSONObject: customObject, options: JSONSerialization.WritingOptions.prettyPrinted) as? [String: Any]
let json = try? JSONSerialization.data(withJSONObject: paymentData, options: JSONSerialization.WritingOptions.prettyPrinted)
The above two code gave crash like Invalid top-level type in JSON write'
And used SwiftyJson library too but that too gave the error SwiftyJSON.SwiftyJSONError.unsupportedType
I wanted to try Codable but that requires me to convert NSNumber to Int and MSMutableArray to Array but I cannot really change this as I have used this code in many other places and also my code is working with Objective C so either I had to use NSNumber or had to convert between Int and NSNumber too many times.
Is there a solution that doesn't require changing the current implementation of class and still convert the object to json.
Any help would be appreciated.

Swift - Convert [[String:Any?]] to Data

I have an [[String:Any]] object populated like:
var result : [[String : Any]] = [[String : Any]]()
And I need convert it to Data.
I'm just using:
JSONEncoder().encode(result)
To convert it.
But I get this error:
Generic parameter 'T' could not be inferred
Exist a simple way to convert a [[String:Any?]] object toData` ?
JSONEncoder can only encode objects whose type conforms to Encodable. If you want to encode Any to JSON, you need to use JSONSerialization to do that.
let jsonData = try? JSONSerialization.data(withJSONObject:result)
You can also using struct for that and using
let data = try? JSONEncoder().encode(struct_Object))

Reading JSON output in Swift

I am trying to read a JSON output from a web app. This output is:
[{"group_name":"XYZ","adminof":0}]
I have a struct that looks like:
struct grouplistStruct{
var group_name : String
var adminof : Any
}
The code that I am using is:
let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as! [Any]
for jsonResult in jsonArray{
let loc = grouplistStruct(group_name: jsonResult["group_name"], adminof: jsonResult["adminof"])
I can see that jsonArray reads the value correctly. Similarly in the for loop, jsonResult is also reading the value correctly
But when I try to assign this value to a Struct variable, it shows an error:
Type 'Any' has no subscript members
Why is that happening? Sorry I am new to Swift so I am learning all this.
Since your json data is an array containing dictionaries like this:
[{"group_name":"XYZ","adminof":0}]
You are getting the error
Type 'Any' has no subscript members
because you are downcasting the json as an array of Any and in swift Any (as the name suggests) represents any type like Int, Double or Dictionary of type [String: String] as you know that Int or Double can not have subscript like someInt["subscript"]
So you need to downcast to this specific type using [[String: Any]] .
This represents an array containing the dictionaries of type [String: Any]. This will work because dictionaries can have subscript members e.g someDict["group_name"]
Hence you should use [[String: Any]] instead of [Any] in this statement try JSONSerialization.jsonObject(with: data, options: []) as! [Any]

Conditionally decoding JSON based on a field in the JSON

I am receiving JSON from an API and the response can be one of 30 types. Each type has a unique set of fields, but all responses have a field type which states which type it is.
My approach is to use serde. I create a struct for each response type and make them decodable. Once I have that how do I choose which struct should be used for a freshly received message?
At the moment, I've created another struct TypeStruct with only a single field for type. I decode the JSON into a TypeStruct, then choose the appropriate struct for received message, based on type value, and decode the message again.
I would like to get rid of this decoding duplication.
You can use the existing enum deserialization. I'll give a step by step example to deserialize your format to the following enum:
#[derive(Debug, PartialEq, Eq, Deserialize)]
enum MyType {
A {gar: ()},
B {test: i32},
C {blub: String},
}
Start with an example json string:
let json = r#"{"type": "B", "test": 42}"#;
Turn it into a Value enum
let mut json: serde_json::Value = serde_json::from_str(json).unwrap();
Rip out the type field
let type_ = {
let obj = json.as_object_mut().expect("object");
let type_ = obj.remove("type").expect("`type` field");
if let serde_json::Value::String(s) = type_ {
s
} else {
panic!("type field not a string");
}
};
Create the "proper" enum json. A struct with a single field where the name of the field is the enum variant and the value of the field is the variant value
let mut enum_obj = std::collections::BTreeMap::new();
enum_obj.insert(type_, json);
let json = serde_json::Value::Object(enum_obj);
Use the generated json deserializer to turn the json into a value of your enum
let obj: MyType = serde_json::from_value(json).unwrap();

How do you create a SwiftyJSON dictionary out of optional AnyObjects?

I recently ran into a very time-consuming issue for a simple task of creating a JSON dictionary with optional objects. I'm using SwiftyJSON.
If I create the following dictionary, I get thrown errors
JSON type doesn't have an initializer that supports Dictionary<String,AnyObject?>
But if I simply change AnyObject to an non-optional type, it works.
var dict: Dictionary <String, AnyObject?> = [
"title" : title as? AnyObject,
"date" : date as? AnyObject,
]
var jsonData: JSON = JSON(dict) // this is where I get the error
I need to actually a JSON data set that potentially has nil values, but it seems like SwiftyJSON doesn't allow it.
Is there any way I can create a JSON dictionary with optional objects using SwiftyJSON?
Neither any key nor any value of a Swift Dictionary can be nil.
You could declare the variable as optional
var dict: Dictionary <String, AnyObject>?