Swift parse json to struct - ios

I have a struct like
struct Channel {
var id : Int = 0
var name = ""
}
and I am getting json from URL as
{"channel_list":[{"channel_id":0,"channel_name":"test1"},{"channel_id":0,"channel_name":"test2"}]}
However I am not able to get data as
func parseJson(anyObj:AnyObject) -> Array<Channel>{
var list:Array<Channel> = []
if anyObj is Array<AnyObject> {
var b:Channel = Channel()
for json in anyObj as! Array<AnyObject>{
b.id = (json["channel_id"] as AnyObject? as? Int) ?? 0
b.name = (json["channel_name"] as AnyObject? as? String) ?? ""
list.append(b)
}
}
return list
}
//read code
let anyObj: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0),error: nil) as AnyObject?
println(anyObj)
if let myobj=anyObj["channel_list"] as AnyObject {
self.Channellist=self.parseJson(anyObj!)
}
Whats wrong with this?

First, instead of using AnyObject, you should cast the JSON response as a Dictionary: [NSObject:AnyObject] then safe cast the result of anyObj["channel_list"] to an Array of Dictionaries [[NSObject:AnyObject]], because this is your JSON response format.
Then you need to use this type in your parseJSON function. We're also simplifying it while we're at it, because there's no need to do weird castings anymore.
Also, you were passing the wrong argument to your function (you used anyObj instead of myObj).
struct Channel {
var id : Int = 0
var name = ""
}
func parseJson(anyObj: [[NSObject:AnyObject]]) -> Array<Channel>{
var list: Array<Channel> = []
var b: Channel = Channel()
for json in anyObj {
b.id = (json["channel_id"] as? Int) ?? 0
b.name = (json["channel_name"] as? String) ?? ""
list.append(b)
}
return list
}
if let anyObj = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(0),error: nil) as? [NSObject:AnyObject] {
if let myobj = anyObj["channel_list"] as? [[NSObject:AnyObject]] {
self.Channellist=self.parseJson(myobj)
}
}
There's still room for improvement: you could create an initializer for your Struct, for example, and also create a typealias for the response types, use map to create the list, etc.

Here's how I would do it with Swift 2:
struct Channel {
var id : Int
var name: String
init?(JSON: [NSObject: AnyObject]?) {
guard let channelID = json["channel_id"] as? Int, let channelName = json["channel_name"] as? String
else { name = ""; id = 0; return nil }
name = channelName
id = channelID
}
}
func parseJSON(array: [[NSObject:AnyObject]]) -> [Channel?] {
return array.map { Channel(JSON: $0) }
// If you don't want to return optionals to channel you can do this instead:
// return array.map { Channel(JSON: $0) }.filter { $0 != nil }.map { $0! }
}
// And in the caller
do {
guard let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [NSObject : AnyObject]
else { throw NSError(domain ... // Setup error saying JSON wasn't parsed. }
guard let arrayContents = dict["channel_list"] as? [[NSObject:AnyObject]]
else { throw NSError(domain ... // Setup error saying array wasn't found. }
let channels = parseJSON(arrayContents)
}
catch {
print(error)
}

Related

What is the proper way to use EVReflection to parse a Dictionary type?

I am using EVReflection in my app. One JSON response should be parsed as type Dictionary<String,Array<MyObject>>. I have successfully parsed this by overriding the setValue method like this:
override func setValue(_ value: Any!, forUndefinedKey key: String) {
switch key {
case "response":
if let dict = value as? NSDictionary {
response = Dictionary<String,Array<MyObject>>();
for (key, value) in dict {
var listValues = Array<MyObject>();
if let array = value as? NSArray {
for vd in array {
listValues.append(MyObject(dictionary: vd as! NSDictionary));
}
}
response![key as? String ?? ""] = listValues;
}
}
break;
}
}
However, I am seeing the following error in the console:
ERROR: Could not create an instance for type Swift.Dictionary<Swift.String, Swift.Array<MyObject>>
Is there a different way I should be doing this? How do I get the error to go away?
I was able to figure this out by using a propertyConverter as follows:
override public func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
return[
(
key: "response"
, decodeConverter: {
if let dict = $0 as? NSDictionary {
self.response = Dictionary<String,Array<MyObject>>();
for (key, value) in dict {
var listValues = Array<MyObject>();
if let array = value as? NSArray {
for vd in array {
listValues.append(MyObject(dictionary: vd as! NSDictionary));
}
}
self.response![key as? String ?? ""] = listValues;
}
}
}
, encodeConverter: { return nil }
)
]
}
With EVReflection you should be using NSDictionary not a Dictionary (which is a struct).
If you do this then you shouldn't need to override any property converter methods.

object list always print nil in swift

In my object Dish xxx.Dish, I want to access the Choice class price and name to display but I failed. dish data load from web API and I tested data loaded success full and put the data to the object dish and it return the object list to viewcontroller to load tableview.
Output of printed console
Optional([xxx.Dish, xxx.Dish])
and in the dish class before append optionList?.append(_obj)
xxx.DishOption
Anyone helps me how can I do that .. I am new to swift and is it right way to implement? Please suggest me?
class Dish {
let dishId : String
var optionList : [DishOption]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let dishId = resposne["dishId"] as? String else {
return nil
}
self.dishId = dishId
if let objs = resposne["options"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = DishOption(fromAPIResponse: obj){
optionList?.append(_obj)
}
}
}
}
class DishOption {
let optionId : String
var choiceList : [Choice]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let optionId = resposne["optionId"] as? String else {
return nil
}
self.optionId = optionId
if let objs = resposne["choices"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = Choice(fromAPIResponse: obj){
choiceList?.append(_obj)
}
}
}
}
}
class Choice{
let choiceId : String
let name : String
let price : String
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let choiceId = resposne["choiceId"] as? String ,
let name = resposne["name"] as? String,
let price = resposne["price"] as? String else {
return nil
}
self.choiceId = choiceId
self.name = name
self.price = price
}
}
UPDATE:
var dishMenuList = [Dish]()
guard let objs = json["menu_list"] as? [[String : AnyObject]] else {
return
}
for obj in objs {
if let _obj = Dish(fromAPIResponse: obj){
print(_obj.optionList) //always print nil
if let options = _obj.optionList {
for data in options {
print(data.displayAsButton)
}
}
dishMenuList.append(_obj)
}
}
From what I can see, you are never initializing both the optionList and choiceList arrays. It would be better to initialize them as empty arrays:
class Dish {
let dishId : String
var optionList = [DishOption]()
...
optionList.append(_obj)
This is the reason that you cannot see any options. Since the optionList is still nil, the line optionList?.append(_obj) does not execute.

Swift Firebase - Contextual type 'AnyObject' cannot be used with dictionary literal

I'm getting this error which I can't figure out how to fix:
Contextual type 'AnyObject' cannot be used with dictionary literal
I've searched on the internet but failed to find an answer. Here's my code:
struct Sweet {
let key:String!
let content:String!
let addedByUser:String!
let itemReft:FIRDatabaseReference?
init (content:String, addedByUser:String, key:String = "") {
self.key = key
self.content = content
self.addedByUser = addedByUser
self.itemReft = nil
}
init (snapshot:FIRDataSnapshot) {
key = snapshot.key
itemReft = snapshot.ref
if let dict = snapshot.value as? NSDictionary, let postContent = dict["content"] as? String {
content = postContent
} else {
content = ""
}
if let dict = snapshot.value as? NSDictionary, let postUser = dict["addedByUser"] as? String {
addedByUser = postUser
} else {
addedByUser = ""
}
}
func toAnyObject() -> AnyObject {
return ["content":content, "addedByUser":addedByUser]
}
The error happens at this line:
return ["content":content, "addedByUser":addedByUser]
I've been following this tutorial iOS Swift Tutorial: Get started with Firebase and an App like Twitter
Thanks for your time!
You have to cast the literal to the desired type
func toAnyObject() -> Any {
return ["content":content, "addedByUser":addedByUser] as Any
}
But - no offense – casting up a specific type to something more unspecific is silly. Why not
func toDictionary() -> [String:String] {
return ["content":content, "addedByUser":addedByUser]
}

Object Mapper - parsing array of [AnyObject]

I have response JSON of multitype objects from API.
It has type property inside. Now I'm trying to apply some kind of automated mapping basing on type property, but I can't make it work in any means.
private let modelClassMap = [
"first_type": First.self
]
func createModelWithDictionary(json: [String: AnyObject]) -> [AnyObject] {
var items: [AnyObject]
if let items = json["items"] as? [[String: AnyObject]] {
for item in items {
if let typeString = item["type"] as? String {
var Type = self.modelClassMap[typeString]
items.append(Mapper<Type>().map(item))
}
}
}
return items
}
error I am getting is that Type is not a type
What you're trying to do is not really possible, because template's associated types are not runtime. Compiler needs to know a type at compile time.
We can do it a bit differently, using enums:
enum ModelClassMap: String {
case FirstType = "first_type"
func map(item: [String: AnyObject]) -> AnyObject? {
switch self {
case FirstType:
return Mapper<First>().map(item)
}
}
}
And in your for-loop you can try convert string to enum:
func createModelWithDictionary(json: [String: AnyObject]) -> [AnyObject] {
var mappedItems: [AnyObject] = []
if let items = json["items"] as? [[String: AnyObject]] {
items.forEach() {
if let typeString = $0["type"] as? String,
let mappedType = ModelClassMap(rawValue: typeString),
let mappedObject = mappedType.map($0) {
// mappedObject represents an instance of required object, represented by "type"
mappedItems.append(mappedObject)
}
}
}
return mappedItems
}

Dynamically remove null value from swift dictionary using function

I have following code for dictionary
var dic : [String: AnyObject] = ["FirstName": "Anvar", "LastName": "Azizov", "Website": NSNull(),"About": NSNull()]
I already remove key which have null value using below code
var keys = dic.keys.array.filter({dic[$0] is NSNull})
for key in keys {
dic.removeValueForKey(key)
}
It works for static dictionary,But I want do it dynamically,I want to done it using function but whenever I pass dictionary as a argument it works as a let means constant so can not remove null key
I make below code for that
func nullKeyRemoval(dic : [String: AnyObject]) -> [String: AnyObject]{
var keysToRemove = dic.keys.array.filter({dic[$0] is NSNull})
for key in keysToRemove {
dic.removeValueForKey(key)
}
return dic
}
please tell me solution for this
Rather than using a global function (or a method), why not making it a method of Dictionary, using an extension?
extension Dictionary {
func nullKeyRemoval() -> Dictionary {
var dict = self
let keysToRemove = Array(dict.keys).filter { dict[$0] is NSNull }
for key in keysToRemove {
dict.removeValue(forKey: key)
}
return dict
}
}
It works with any generic types (so not limited to String, AnyObject), and you can invoke it directly from the dictionary itself:
var dic : [String: AnyObject] = ["FirstName": "Anvar", "LastName": "Azizov", "Website": NSNull(),"About": NSNull()]
let dicWithoutNulls = dic.nullKeyRemoval()
Swift 5 adds compactMapValues(_:), which would let you do
let filteredDict = dict.compactMapValues { $0 is NSNull ? nil : $0 }
For Swift 3.0 / 3.1 this could be helpful. Also removes NSNull objects recursive:
extension Dictionary {
func nullKeyRemoval() -> [AnyHashable: Any] {
var dict: [AnyHashable: Any] = self
let keysToRemove = dict.keys.filter { dict[$0] is NSNull }
let keysToCheck = dict.keys.filter({ dict[$0] is Dictionary })
for key in keysToRemove {
dict.removeValue(forKey: key)
}
for key in keysToCheck {
if let valueDict = dict[key] as? [AnyHashable: Any] {
dict.updateValue(valueDict.nullKeyRemoval(), forKey: key)
}
}
return dict
}
}
Swift 3+: Remove null from dictionary
func removeNSNull(from dict: [String: Any]) -> [String: Any] {
var mutableDict = dict
let keysWithEmptString = dict.filter { $0.1 is NSNull }.map { $0.0 }
for key in keysWithEmptString {
mutableDict[key] = ""
}
return mutableDict
}
Use:
let outputDict = removeNSNull(from: ["name": "Foo", "address": NSNull(), "id": "12"])
Output: ["name": "Foo", "address": "", "id": "12"]
Nested NSNull supported
To remove any NSNull appearance in any nested level (including arrays and dictionaries), try this:
extension Dictionary where Key == String {
func removeNullsFromDictionary() -> Self {
var destination = Self()
for key in self.keys {
guard !(self[key] is NSNull) else { destination[key] = nil; continue }
guard !(self[key] is Self) else { destination[key] = (self[key] as! Self).removeNullsFromDictionary() as? Value; continue }
guard self[key] is [Value] else { destination[key] = self[key]; continue }
let orgArray = self[key] as! [Value]
var destArray: [Value] = []
for item in orgArray {
guard let this = item as? Self else { destArray.append(item); continue }
destArray.append(this.removeNullsFromDictionary() as! Value)
}
destination[key] = destArray as? Value
}
return destination
}
}
Swift 4
A little more efficient than the other solutions. Uses only O(n) complexity.
extension Dictionary where Key == String, Value == Any? {
var trimmingNullValues: [String: Any] {
var copy = self
forEach { (key, value) in
if value == nil {
copy.removeValue(forKey: key)
}
}
return copy as [Key: ImplicitlyUnwrappedOptional<Value>]
}
}
Usage: ["ok": nil, "now": "k", "foo": nil].trimmingNullValues // =
["now": "k"]
If your dictionary is mutable you could do this in place and prevent the inefficient copying:
extension Dictionary where Key == String, Value == Any? {
mutating func trimNullValues() {
forEach { (key, value) in
if value == nil {
removeValue(forKey: key)
}
}
}
}
Usage:
var dict: [String: Any?] = ["ok": nil, "now": "k", "foo": nil]
dict.trimNullValues() // dict now: = ["now": "k"]
The cleanest way to do it, just 1 line
extension Dictionary {
func filterNil() -> Dictionary {
return self.filter { !($0.value is NSNull) }
}
}
Rather than using a global function (or a method), why not making it a method of Dictionary, using an extension?
extension NSDictionary
{
func RemoveNullValueFromDic()-> NSDictionary
{
let mutableDictionary:NSMutableDictionary = NSMutableDictionary(dictionary: self)
for key in mutableDictionary.allKeys
{
if("\(mutableDictionary.objectForKey("\(key)")!)" == "<null>")
{
mutableDictionary.setValue("", forKey: key as! String)
}
else if(mutableDictionary.objectForKey("\(key)")!.isKindOfClass(NSNull))
{
mutableDictionary.setValue("", forKey: key as! String)
}
else if(mutableDictionary.objectForKey("\(key)")!.isKindOfClass(NSDictionary))
{
mutableDictionary.setValue(mutableDictionary.objectForKey("\(key)")!.RemoveNullValueFromDic(), forKey: key as! String)
}
}
return mutableDictionary
}
}
Swift 4 example using reduce
let dictionary = [
"Value": "Value",
"Nil": nil
]
dictionary.reduce([String: String]()) { (dict, item) in
guard let value = item.value else {
return dict
}
var dict = dict
dict[item.key] = value
return dict
}

Resources