justADummy
justADummy

Reputation: 62

Swift Dynamic json Values Reading and Writing

Problem Stuck On

I am trying to be able to read out my json file data to the console log for testing so I can use it later on.

Im not sure how to finish off my other structs due to varying data size and possible bad json format.

Once I finish that I believe I would need to use a for loop for read the varying size of data from "M", "S" and "WP" (This part shouldn't be complicated I believe)

Possible Things to Consider

I want to write and add data to "M" "S" "WP"

The data amount for ("M", "S") could be any number of String Array data objects

The data in "WP" Might need a different format I would like to add a name("abc") with a Int array containing any number of data points

Note: My Json Format Might Be Wrong in Some Areas concerning MP and WP

Swift Code To Grab Data

import Foundation


struct UserDay: Codable {
    let mp: UserMP
    let wp: UserWP
}

struct UserMP: Codable {
    let m: [UserM]
    let s: [UserS]
}

struct UserM : Codable {
    let title: String
    let description: String
    let time: String
}
struct UserS : Codable {
    let title: String
    let description: String
    let time: String
}


struct UserWP: Codable {
    let wp: [WPData]
}

struct WPData: Codable {
    let title: String
    let values: [Int]
}


class LogDataHandler {
    
    
    public func grabJSONInfo(){
        guard let jsonURL = Bundle(for: type(of: self)).path(forResource: "LogData", ofType: "json") else { return }
        
        guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: jsonURL), encoding: String.Encoding.utf8) else { return }
        
        // Print Info for TESTING
        var year: UserDay?
        
        do {
            year = try JSONDecoder().decode(UserDay.self, from: Data(jsonString.utf8))
        } catch {
            print("ERROR WHEN DECODING JSON")
        }
        
        guard let results = year else {
            print("YEAR IS NIL")
            return
        }
        
        print(results)
        
    }
    
    
}

JSON Example Data Below

{
    "01/01/2020": {
   
        "MP" : {
            "M" : [
                {"title" : "m1", "description" : "1", "time" : "12:30pm"},
                {"title" : "m2", "description" : "2", "time" : "1:30pm"},
                {"title" : "m3", "description" : "3", "time" : "2:30pm"}
            ],
            "S" : [
                {"title" : "s1", "description" : "1", "time" : "1pm"}
            ]
        },
        "WP" : [
            { "title" : "abc", "values" :  [12, 10, 6]},
            { "title" : "def", "values" :  [8]}
        ]
    },
    "01/29/2020": {
        
        "MP" : {
            "M" : [{"title" : "m1", "description" : "1", "time" : "12:30pm"}],
            "S" : [{"title" : "s1", "description" : "1", "time" : "12:30pm"}]
        },
        "WP" :[{ "title" : "def", "values" :  [8]}]
    }
    
}

Upvotes: 1

Views: 642

Answers (1)

New Dev
New Dev

Reputation: 49590

Based on the comments and our chat, this seems to be a question of the right way to construct the Swift models and the JSON object.

Based on your (updated) JSON, you might want to decode your data into a [String: UserDay] - a dictionary with a date string as key and UserDay as a value.

First, WP property in your JSON is just an array of objects (that map to WPData), so it's best to change your UserDay.wp to be [WPData] instead of UserWP:

struct UserDay: Codable {
    let mp: UserMP
    let wp: [WPData] // <-- changed
}

Second, some of your models' properties don't match directly to what's in JSON because keys-properties mapping is case sensitive. You can explicitly define CodingKeys to map them:

struct UserDay: Codable {
    let mp: UserMP
    let wp: [WPData]

    enum CodingKeys: String, CodingKey {
        case mp = "MP", wp = "WP"
    }
}

struct UserMP: Codable {
    let m: [UserM]
    let s: [UserS]

    enum CodingKeys: String, CodingKey {
        case m = "M", s = "S"
    }
}

Now you're ready to decode [String: UserDay]:

let userDays = try JSONDecoder().decoder([String: UserDay].self, from: jsonData)

let userDay = userDays["01/29/2020"]


Of course, working with String instead of Date isn't very convenient. Unfortunately, Dictionary's conformance to Codable only supports Int or String as keys (AFAIK).

So, let's do a manual decoding into a new root object UserData that works with Dates:

struct UserData: Codable {
   var userDays: [Date: UserDay]

   init(from decoder: Decoder) throws {
       let container = try decoder.singleValueContainer()
       let dict = try container.decode([String: UserDay].self)

       let dateFormatter = DateFormatter()
       dateFormatter.dateFormat = "MM/dd/yyyy"
       dateFormatter.locale = Locale(identifier: "en_US_POSIX")

       // decode (String, UserDay) pairs into an array of (Date, UserDay)
       let pairs = dict.compactMap { (key, value) -> (Date, UserDay)? in
           guard let date = dateFormatter.date(from: key) else { return nil }
           return (date, value)
       }

       // uniquing is used just in case there non unique keys
       self.userDays = Dictionary(pairs, uniquingKeysWith: {(first, _) in first})
   }
}

Now, we can decode into this UserData object:

let userData = try JSONDecoder().decode(UserData.self, from: jsonData)

let todaysData = userData.userDays[Date()]

Upvotes: 1

Related Questions