Updating json model and retaining/converting existing data - Swift - json

I have a data model which looks as such:
struct MacroLog : Codable {
var date: Date = Date()
var type: MacroType
var beforeValue: Int
var afterValue: Int
var description: String
var isOverwrite: Bool = false
func valueDifference() -> Int {
return afterValue - beforeValue
}
}
And I need to update it with adding a timestamp property as such:
struct MacroLog : Codable {
let timestamp: Date
var date: Date = Date()
var type: MacroType
var beforeValue: Int
var afterValue: Int
var description: String
var isOverwrite: Bool = false
func valueDifference() -> Int {
return afterValue - beforeValue
}
}
What would I need to do, to have my existing MacroLog entities retained or converted to the new model data? I know it stays retained so this is basically me asking how I properly manage making model changes of any kind in these situations.
I update new entries the following way:
private func updateLogs(logs: [MacroLog]) {
var logsData = Data()
do {
let encoder = JSONEncoder()
logsData = try encoder.encode(logs)
_ = (logsData as NSData).write(to: fileUrl as URL, atomically: true)
} catch {
print("failed - \(error)")
}
}

My question was solved by the answer in the comment about making the timestamp optional. That way, all my previous log entries are retained and loaded going forward.

Related

Swift: Decode flat JSON to an structured object

wonder if there is a simple way to get a simple flat json to a struct with a structure
json:
{
"date": "2022-02-24T00:00:00.000Z",
"personel": 800,
"plane": 7,
"drone": 6,
}
what I want is to get a structure like this:
struct Day: Codable, Identifiable {
var id = UUID()
var date : String
var dayData: [DayData]
struct DayData: Codable {
var personel: Int
var plane: Int
var drone: Int
}
}
I thought there should be some simple way to make it work
As specified in comments it would be better to create temporary DBObject and initialize your Day object with it.
Simple DBObject:
struct DBObject: Decodable {
let date: String?
let personel, plane, drone: Int?
}
Adding init() to your Day
struct Day: Codable, Identifiable {
var id = UUID()
var date : String
var dayData: [DayData]
init(from dbObject: DBObject) {
let dayData = DayData(personel: dbObject.personel ?? 0,
plane: dbObject.plane ?? 0,
drone: dbObject.drone ?? 0)
self.dayData = [dayData]
self.date = dbObject.date ?? ""
}
struct DayData: Codable {
var personel: Int
var plane: Int
var drone: Int
}
}
Decoding Day from Data response:
func returnDay(from dataResponse: Data) -> Day {
do {
let decoder = JSONDecoder()
let dbObject = try decoder.decode(DBObject.self,
from: dataResponse)
return Day(from: dbObject)
} catch {
fatalError("Cannot decode object")
}
}

SwiftUI parse a json from an URL with entry values

I'm stuck when a I need to load a json file to swiftUI when this json comes from an URL with entry variables that must be filled by the user before getting the data.
I have the following code:
// This is the structure to get the data from the API.
struct loka_result: Codable {
var date: String
var time: String
var unix_time: Int
var seqNumber : Int
var lat: Double
var lng: Double
var device: String
var accuracy: Double
var info: String
var temperature : Double?
var battery_voltage : Float?
}
// Now conform to Identifiable
extension loka_result: Identifiable {
public var id: Int { return unix_time }
}
// Model Data to get the data
final class ModelData: ObservableObject {
#Published var allthelokas : [loka_result] = loadAllData(deviceId:selected_loka, timeDelta: selected_delta)
}
// Function to get and parse the data
func loadAllData<T: Decodable>(deviceId:String,timeDelta:Int) -> T
{
let url = URL(string: "https://xxxx.php?device=\(deviceId)&hours=\(timeDelta)"),
data = try? Data(contentsOf: url!)
return
try! JSONDecoder().decode(T.self, from: data!)
}
Then in TempChartView I would like to get a list
import SwiftUI
struct TempChartNew: View {
#EnvironmentObject var modelData: ModelData
var body: some View {
VStack {
List(modelData.allthelokas) { j in
// ForEach(modelData.allthelokas) { jj in
HStack {
Text(j.device)
Text(j.date)
Text(j.time)
Text(String(j.seqNumber))
}
}
}
}
}
struct TempChartNew_Previews: PreviewProvider {
static var previews: some View {
TempChartNew().environmentObject(ModelData())
}
}
all this its working because just to test it I have added two global variables to fill in the data for the function.
var selected_loka: String = "3JJJ30"
var selected_delta: Int = 24
But the ideia would be for the view to have two entry points.
And its here where I'm stuck ! I don't know how to pass the variables to the class.
Does anyone can please point me in the correct direction ? - Thank you

Handling 2 keys for same value in single class

I have a use case in which I get score form JSON using
let score = json["score_test"].arrayValue.map {Score.decode(json: $0)}
I have to reuse this class for a response in which only key for the value changes i.e
let score = json["score"].arrayValue.map {Score.decode(json: $0)}
Is there a way to achieve this so that I get the data of Score object whether the key is score_test or score depending on the JSON using the same class?
Also I tried using nil check but since the object is initialized that is not working.
Model of Score:
class Score: Object, Decoder {
dynamic var id: String = ""
dynamic var title: String = ""
dynamic var body: String = ""
dynamic var cardOrder: Int = 0
dynamic var video: Video? = nil
override static func primaryKey() -> String? {
return "id"
}
typealias T = Score
// MARK: Decoder method
static func decode(json: JSON) -> Score {
let id = json["_id"].stringValue
let title = json["title"].stringValue
let body = json["data"].stringValue
let cardOrder = json["card_order"].intValue
var video: Video?
if (json["video"].exists()) {
video = Video.decode(json: json["video"])
}
let score = Score()
score.id = id
score.title = title
score.body = body
score.video = video
score.cardOrder = cardOrder
return score
}
}
From what I understand, score is of type [Score] so what I'd do is:
var score = json["score_test"].arrayValue.map {Score.decode(json: $0)}
if score.isEmpty {
score = json["score"].arrayValue.map {Score.decode(json: $0)}
}

Accessing a variable buried deep inside a function to use outside the function in Swift 2

So I've run into a tiny obstacle and I'm trying to access a variable created inside an Alamofire request function. A bit of background into:
Used SwiftyJSON/Alamofire to access JSON file and parse it, have a variable for a accessing date, but date was in RFC 3339 format and now I created a function to parse the date from RFC 339 to a readable format but i don't now how to access the date variable created in the JSON parse function to use with the Date parse function.
//Get the JSON from server
func getJSON() {
Alamofire.request(.GET, "link goes here").responseJSON { (Response) in
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let date: String? = anItem["Date"].stringValue //trying to access this variable outside the function
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableDate.append(date!)
self.tableBody.append(body!)
print(anItem["Title"].stringValue)
print(anItem["Date"].stringValue)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
// this date stuff isn't being used yet, because I have no idea how...
public func dateForRFC3339DateTimeString(rfc3339DateTimeString: String) -> NSDate? {
let enUSPOSIXLocale = NSLocale(localeIdentifier: "en_US_POSIX")
let rfc3339DateFormatter = NSDateFormatter()
rfc3339DateFormatter.locale = enUSPOSIXLocale
rfc3339DateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
rfc3339DateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
return rfc3339DateFormatter.dateFromString(rfc3339DateTimeString)
}
public func userVisibleDateTimeStringForRFC3339DateTimeString(rfc3339DateTimeString: String) -> String? {
let maybeDate = dateForRFC3339DateTimeString(rfc3339DateTimeString)
if let date = maybeDate {
let userVisibleDateFromatter = NSDateFormatter()
userVisibleDateFromatter.dateStyle = NSDateFormatterStyle.MediumStyle
userVisibleDateFromatter.timeStyle = NSDateFormatterStyle.ShortStyle
return userVisibleDateFromatter.stringFromDate(date)
} else {
return nil
}
}
let finalDateStr = userVisibleDateTimeStringForRFC3339DateTimeString(MasterViewController) //now this is where it gets weird, instead of letting me enter the string in the brackets, it defaults to MasterViewController, now I tried to move the date functions to another .swift file (an empty one) and it doesn't do that anymore
So yeah, that's about it, if anyone could help, it would be greatly appreciated.
Try below code, let me know if it works:
func dateForRFC3339Date(rfc3339Date: NSDate) -> NSDate? {
let enUSPOSIXLocale = NSLocale(localeIdentifier: "en_US_POSIX")
let rfc3339DateFormatter = NSDateFormatter()
rfc3339DateFormatter.locale = enUSPOSIXLocale
rfc3339DateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
rfc3339DateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
let dateString = rfc3339DateFormatter.stringFromDate(rfc3339Date)
return rfc3339DateFormatter.dateFromString(dateString)
}
And call it in your getJSON() method like:
let convertedDate = self.dateForRFC3339Date(rfc3339Date: anItem["Date"])

Initialize Swift class with AnyObject? from NSJSONSerialization

I am using NSJSONSerialization in Swift 1.2 to parse some json that is returned from an API response.
var err: NSError?
let opts = NSJSONReadingOptions.AllowFragments
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(jsonData!, options: opts, error: &err)
The parsed json is provided as AnyObject?. I would like to use this optional to initialize a class object which can be used as the model data in an application.
class Alerts {
let type: String
let date: String
let description: String
let expires: String
let message: String
init(json: AnyObject) {
if let
jsonDict = json as? [String: AnyObject],
alertsArray = jsonDict["alerts"] as? [AnyObject],
alertsDict = alertsArray[0] as? [String: AnyObject],
type = alertsDict["type"] as? String,
date = alertsDict["date"] as? String,
description = alertsDict["description"] as? String,
expires = alertsDict["expires"] as? String,
message = alertsDict["message"] as? String
{
self.type = type
self.date = date
self.description = description
self.expires = expires
self.message = message
}
else
{
self.type = "err"
self.date = "err"
self.description = "err"
self.expires = "err"
self.message = "err"
}
}
}
// example of creating a data model from the json
let alerts = Alerts(json: json!)
alerts.type
alerts.date
alerts.description
alerts.expires
alerts.message
Since NSJSONSerialization returns an optional, I have to check for the existence of each value type as I extract the json data. As you can see in the above code, I used the improved optional bindings from Swift 1.2 to clean up the init method. Without using third-party libraries, is there anything else I can do to the class model (enums, structs, type aliases) to make it more readable? Should I use a struct for the model data instead of a class? Would it be possible to create a custom type using an enum or struct to represent a json object?
So without using third party libraries, the if let trees are usually the best practice, which you have shown. To help you later down the road, maybe recreate your object hierarchy in JSON as a Struct model in Swift. Something like:
var json = JSON(JSONData.sharedjson.jsonRaw!)
var mongoIdTest = json["resultset"]["account"]["mongoId"].string
struct Root {
var timestamp: Int?
var resultset = ResultSet()
init() {
self.timestamp = json["timestamp"].int
println(json)
}
}
struct ResultSet {
var alert: String?
var account = Account()
var customer = Customer()
init() {
}
}
struct Account {
var mongoId: String?
init() {
mongoId = json["resultset"]["account"]["mongoId"].string
}
}
struct Locations {
}
struct Customer {
var account: String?
var address: String?
var id: String?
var loginId: String?
var mongoId: String?
var name: String?
var opco = Opco()
init() {
account = json["resultset"]["customer"]["account"].string
address = json["resultset"]["customer"]["address"].string
id = json["resultset"]["customer"]["id"].string
loginId = json["resultset"]["customer"]["loginId"].string
mongoId = json["resultset"]["customer"]["mongoId"].string
name = json["resultset"]["customer"]["name"].string
}
}
struct Opco {
var id: String?
var phone: String?
var cutOffTime: String?
var name: String?
var payerId: String?
init() {
id = json["resultset"]["customer"]["opco"]["id"].string
phone = json["resultset"]["customer"]["opco"]["phone"].string
cutOffTime = json["resultset"]["customer"]["opco"]["cutOffTime"].string
name = json["resultset"]["customer"]["opco"]["name"].string
payerId = json["resultset"]["customer"]["opco"]["payerId"].string
}
}
This way you can still use autocomplete and dot notation to navigate through your hierarchy.
Edit: I have a data structure from an actual project I've worked on added to the answer, hopefully this gives a better idea. Keep in mind that I'm using SwiftyJSON for the JSON() call.
Edit 2:
This is a method I found for getting JSON info into a Swift dictionary without the use of some other library. I'm not sure there is another way to do it that's easier without the use of third party libraries.
var urlToRequest = "https://EXAMPLE.com/api/account.login?username=MY_USERNAME&password=Hunter2"
if let json = NSData(contentsOfURL: NSURL(string: urlToRequest)!) {
// Parse JSON to Dictionary
var error: NSError?
let boardsDictionary = NSJSONSerialization.JSONObjectWithData(json, options: NSJSONReadingOptions.MutableContainers, error: &error) as? Dictionary<String, AnyObject>
fulljson = boardsDictionary
// Display all keys and values
println("Keys in User Data:")
for (key, value) in boardsDictionary! {
println("\(key)-------\(value)")
}
println(fulljson?["resultset"])
}
else {
println("Test JSON nil: No Connection?")
}
That dictionary will be the input for your Structs.