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.