Home > Blockchain >  Having trouble with print output of object decoded with JSONDecoder
Having trouble with print output of object decoded with JSONDecoder

Time:12-04

I'm trying to decode a JSON string in swift but having some weird issues accessing the properties once decoded.

This is the contents of the JSON file that I retrieve from a locally stored JSON file

[
  {
    "word": "a",
    "usage": [
      {
        "partOfSpeech": "determiner"
      }
    ]
  }
]

And this is the code to access the properties of the JSON file

struct WordDictionary : Codable {
    var word: String
    var usage: [Usage]
}

struct Usage: Codable {
    var partOfSpeech: String
}
                
if let url = Bundle.main.url(forResource: FILE_NAME, withExtension: "json") {
    do {
        let data = try Data(contentsOf: url)
        let decoder = JSONDecoder()
        
        let jsonData = try decoder.decode([WordDictionary].self, from: data)
        print(jsonData[0].word) //Outputs "a"
        print(jsonData[0].usage) //Outputs "[MyApp.AppDelegate.(unknown context at $102a37f00).(unknown context at $102a38038).Usage(partOfSpeech: "determiner")]"

    } catch {
        print("error:\(error)")
    }
}

As you can see, when I try to print(jsonData[0].usage) I get a series of unknown data messages before I get the “Usage” property. When I print this line I just want to see determiner, I’m not sure what the preamble about the “unknown context” is all about.

I’m also running this code in didFinishLaunchingWithOptions function of the AppDelegate.

I’m not sure what I’m missing. I've been trying to find a solution for a few days now and trying different approaches but still can’t get the desired output, any help would be appreciated.

CodePudding user response:

tl;dr

Your types are defined within a function. Either move those type definitions outside of the function or implement your own CustomStringConvertible conformance.


It's a matter of where you defined your types.

Consider:

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        struct WordDictionary: Codable {
            var word: String
            var usage: [Usage]
        }

        struct Usage: Codable {
            var partOfSpeech: String
        }

        do {
            let url = Bundle.main.url(forResource: "test", withExtension: "json")!
            let data = try Data(contentsOf: url)
            let words = try JSONDecoder().decode([WordDictionary].self, from: data)
            print(words[0].usage)
        } catch {
            print(error)
        }

        return true
    }

    ...
}

That produces:

[MyApp.AppDelegate.(unknown context at $102bac454).(unknown context at $102bac58c).Usage(partOfSpeech: "determiner")]

That is saying that Usage was defined in some unknown context within the AppDelegate within MyApp. In short, it does not know how to represent the hierarchy for types defined within functions.

Contrast that with:

class AppDelegate: UIResponder, UIApplicationDelegate {

    struct WordDictionary: Codable {
        var word: String
        var usage: [Usage]
    }

    struct Usage: Codable {
        var partOfSpeech: String
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        do {
            let url = Bundle.main.url(forResource: "test", withExtension: "json")!
            let data = try Data(contentsOf: url)
            let words = try JSONDecoder().decode([WordDictionary].self, from: data)
            print(words[0].usage)
        } catch {
            print(error)
        }

        return true
    }

    ...
}

Which produces:

[MyApp.AppDelegate.Usage(partOfSpeech: "determiner")]


You can also add your own CustomStringConvertible conformance:

struct WordDictionary: Codable {
    var word: String
    var usage: [Usage]
}

struct Usage: Codable {
    var partOfSpeech: String
}

extension Usage: CustomStringConvertible {
    var description: String { "Usage(partOfSpeech: \"\(partOfSpeech)\")" }
}

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        do {
            let url = Bundle.main.url(forResource: "test", withExtension: "json")!
            let data = try Data(contentsOf: url)
            let words = try JSONDecoder().decode([WordDictionary].self, from: data)
            print(words[0].usage)
        } catch {
            print(error)
        }

        return true
    }

    ...
}

Which produces:

[Usage(partOfSpeech: "determiner")]

Through CustomStringConvertible, you can make the print format it however you want.

CodePudding user response:

If you want a type to print nicely when you interpolate an instance of it into a string, you need to make it conform to CustomStringConvertible.

This protocol declares one property: description and when string interpolation encounters an object that conforms to it, it uses the string returned by description instead.

You need something like:

extension Usage: CustomStringConvertible
{
    var description: String 
    {
        return "{ \"partOfSpeech\" : \"\(partOfSpeech)\" }"
    }
}

if you want a JSON like* string to print.

An alternative would be to re-encode the object using a JSONEncoder and convert the data to a String. That's much heavier but might be a better option for more complex objects.

*JSON like because things like line feeds and tabs won't be replaced by escapes and " and \ appearing in the string won't be escaped.

  • Related