-1

I have json data like this code below:

{
"all": [
    {
        "ModelId": 1,
        "name": "ghe",
        "width": 2
    },
    {
        "ModelId": 2,
        "name": "ban",
        "width": 3
    }]
}

I try to get the modelId and convert it to String but it's not working with my code:

let data = NSData(contentsOf: URL(string: url)!)
                do {
                    if let data = data, let json = try JSONSerialization.jsonObject(with: data as Data) as? [String: Any], let models = json["all"] as? [[String:Any]] {
                        for model in models {
                            if let name = model["ModelId"] as? String {
                                _modelList.append(name)
                            }
                        }
                    }
                    completion(_modelList)
                }catch {
                    print("error")
                    completion(nil)
                }

How to fix this issue? Thanks.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Duc Phan
  • 181
  • 5
  • 16
  • Yeah, fix the key name and create a String rather than cast to it. Something like: `if let name = String(model["ModelId"])...`, although you'll probably want to do it in two steps since it will be an optional Integer. – EricS Mar 06 '18 at 17:21
  • @EricS it still not working... – Duc Phan Mar 06 '18 at 17:43
  • @DucPhan ModelID is an integer not a String – Leo Dabus Mar 06 '18 at 17:58
  • 2
    Note that you shouldn't use NSData(contentsOf:URL) to fetch non local resource files synchronously. You should use URLSession dataTask(with: URL) to fetch it asynchronously. – Leo Dabus Mar 06 '18 at 18:01

5 Answers5

0

Currently you are looking for a wrong key ,

 for model in models {
      if let name = model["ModelId"] as? NSNumber {
           _modelList.append(name.stringValue)
       }
  }
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • This won't work since the model id values are numbers, not strings. – rmaddy Mar 06 '18 at 17:25
  • I'm sorry for the price key in my post is wrong, but when I edit the key with ModelId it still not working... – Duc Phan Mar 06 '18 at 17:43
  • @Sh_Khan OP json's root object is a dictionary not an array – Leo Dabus Mar 06 '18 at 18:06
  • @Sh_Khan exactly it is a dictionary. OP can cast the whole json `as? [String: [[String: Any]]]` – Leo Dabus Mar 06 '18 at 18:08
  • @LeoDabus ModelId is considered an item in 0 index of array accessed from all key , if you looked to key all it's in same thing outer is { , which means json is array – Shehata Gamal Mar 06 '18 at 18:11
  • @Sh_Khan I have already converted the JSON string to data here and again the root object it IS a dictionary. Your comment suggesting `jsonObject(with: data as Data) as? [Any] ` is wrong. It will always fail. – Leo Dabus Mar 06 '18 at 18:12
  • @LeoDabus , i tried it in answer he says no output , currently i'm not setting on mac , try the answer also – Shehata Gamal Mar 06 '18 at 18:16
0

I think ModelId is integer type. So, can you try to cast it to Integer

for model in models {
      if let name = model["ModelId"] as? Int{
           _modelList.append("\(name)")
       }
  }

Hope, it will help you.

Harish Singh
  • 765
  • 1
  • 12
  • 23
0

if let as? is to unwrap, not type casting. So you unwrap first, then you cast it into string.

   for model in models {
      if let name = model["ModelId"] as? Int {
          _modelList.append("\(name)")
      }
   }
marko
  • 1,721
  • 15
  • 25
  • What do you mean??? It is a conditional cast from `Any` to `Int`. Btw String interpolation it is not a cast – Leo Dabus Mar 06 '18 at 18:24
0

As long as you are using JSONSerialization.jsonObject to parse your JSON you have very little control over the type the deserialiser will create, you basically let the parser decide. Sensible as it is it will create "some kind of " NSNumber of an Int type from a number without quotes. This can not be cast to a String, therefore your program will fail.

You can do different things in order to "fix" this problem, I would like to suggest the Codable protocol for JSON-parsing, but this specific problem can probably only be solved using a custom initialiser which looks kind of verbose as can be seen in this question.

If you just want to convert your NSNumber ModelId to a String you will have to create a new object (instead of trying to cast in vain). In your context this might simply be

if let name = String(model["ModelId"]) { ...

This is still not an elegant solution, however it will solve the problem at hand.

Patru
  • 4,481
  • 2
  • 32
  • 42
0

another approach is:

import Foundation

struct IntString: Codable
{
    var value: String = "0"
    
    init(from decoder: Decoder) throws
    {
        // get this instance json value
        let container = try decoder.singleValueContainer()
        
        do
        {
            // try to parse the value as int
            value = try String(container.decode(Int.self))
        }
        catch
        {
            // if we failed parsing the value as int, try to parse it as a string
            value = try container.decode(String.self)
        }
    }

    func encode(to encoder: Encoder) throws
    {
      var container = encoder.singleValueContainer()
      try container.encode(value)
    }
}

my solution is to create a new struct that will be able to receive either a String or and Int and parse it as a string, this way in my code i can decide how to treat it, and when my server sends me sometimes an Int value and sometimes a json with that same key as a String value - the parser can parse it without failing

of course you can do it with any type (date / double / float / or even a full struct), and even insert it with some logic of your own (say get the string value of an enum based on the received value and use it as index or whatever)

so your code should look like this:

import Foundation

struct Models: Codable {
    let all: [All]
}

struct All: Codable {
    let modelID: IntString
    let name: String
    let width: IntString

    enum CodingKeys: String, CodingKey {
        case modelID = "ModelId"
        case name = "name"
        case width = "width"
    }
}

parse json into the Models struct:

let receivedModel: Decodable = Bundle.main.decode(Models.self, from: jsonData!)

assuming you'r json decoder is:

import Foundation

extension Bundle
{
    func decode<T: Decodable>(_ type: T.Type, from jsonData: Data, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T
    {

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy

        do
        {
            return try decoder.decode(T.self, from: jsonData)
        }
        catch DecodingError.keyNotFound(let key, let context)
        {
            fatalError("Failed to decode \(jsonData) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
        }
        catch DecodingError.typeMismatch(let type, let context)
        {
            print("Failed to parse type: \(type) due to type mismatch – \(context.debugDescription) the received JSON: \(String(decoding: jsonData, as: UTF8.self))")
            fatalError("Failed to decode \(jsonData) from bundle due to type mismatch – \(context.debugDescription)")
        }
        catch DecodingError.valueNotFound(let type, let context)
        {
            fatalError("Failed to decode \(jsonData) from bundle due to missing \(type) value – \(context.debugDescription)")
        }
        catch DecodingError.dataCorrupted(_)
        {
            fatalError("Failed to decode \(jsonData) from bundle because it appears to be invalid JSON")
        }
        catch
        {
            fatalError("Failed to decode \(jsonData) from bundle: \(error.localizedDescription)")
        }
    }
}
Shaybc
  • 2,628
  • 29
  • 43