NewDev
NewDev

Reputation: 257

JSONSerialization of Custom Object Array

I have a custom class

public class Balance: NSObject {
   var details: String
   var date:    Date
   var amount:  Double
}

I have tried as a struct and as a class both fail

I have an array of balances list: [Balance]

Now I'm need to convert this array into a JSON String something like

[ {details = "text"; date = "2016-11-20"; amount = 0;} ,
  {details = "text2"; date = "2016-11-25"; amount= 10;} ]

I also need to be able to convert the String back into the array. But I can't even get the array to JSON string to work

var resStr = ""
var list: [Balance] 
do {
   let data = try JSONSerialization.data(withJSONObject: list,  options: .prettyPrinted)
   resStr   = String.init(data: data, encoding: .utf8) ?? ""
} catch { fatalError("Error BALANCE HISTORY ")  }

Fails with 'Invalid type in JSON write (Balance)'

Please advise Thanks in advance

Upvotes: 2

Views: 6150

Answers (4)

Thisura Dodangoda
Thisura Dodangoda

Reputation: 741

Since others have pointed out that you can achieve the desired effect by first converting your object to a Dictionary, I will provide a method to achieve what is required by using JSONEncoder instead.

If you maintain the values you need to represent in the JSON encoded string (for example the date as a formatted String), the only required bits are,

  • Make your type conform to Codable
  • Create a CodingKeys enum that represent the JSON keys for your type's properties.

Please see below for an example that applies to your object.

public class Balance: NSObject, Codable {
    var details: String
    // Used to represent formatted date.
    var dateString: String
    var date:    Date = Date(){
        didSet{
            updateDateString()
        }
    }
    var amount:  Double

    enum CodingKeys: String, CodingKey{
        case details, dateString, amount
    }
    
    init(_ d: String, amt: Double){
        details = d
        dateString = ""
        date = Date()
        amount = amt
        
        super.init()
        updateDateString()
    }
    
    private func updateDateString(){
        let df = DateFormatter()
        df.locale = Locale(identifier: "en_US_POSIX")
        df.dateFormat = "yyyy-MM-dd"
        dateString = df.string(from: date)
    }
}

var arr: [Balance] = [
    Balance("Shared Balance", amt: 100),
    Balance("Coupon Balance", amt: 120),
    Balance("Account Balance", amt: 150)
]

do{
    let data = try JSONEncoder().encode(arr)

    // Convert to JSON Encoded String
    let str = String(data: data, encoding: .utf8)!
    print(str)
    
    // Convert back from JSON Encoded String
    let raw = try JSONDecoder().decode([Balance].self, from: str.data(using: .utf8)!)
    print(raw)
}
catch(let err){
    print(err)
}

The additional updateDateString() boiler plate code is to produce the String with the date format, "yyyy-MM-dd".

The above code can product a JSON encoded version of your Balance array as follows.

[{"details":"Shared Balance","amount":100,"dateString":"2021-01-14"},
{"details":"Coupon Balance","amount":120,"dateString":"2021-01-14"},
{"details":"Account Balance","amount":150,"dateString":"2021-01-14"}]

The key takeaway is to use JSONEncoder instead of JSONSerialization.

Hope I helped!

Upvotes: 1

BitParser
BitParser

Reputation: 3968

Well, I believe first you need to convert your object to some form of dictionary.

Let me show you an example:

class Balance: NSObject {
    var details: String
    var date: Date
    var amount: Double

    func asDictionary() -> [String: AnyObject] {
        return ["details": details as AnyObject, "date": "\(date)" as AnyObject, "amount": amount as AnyObject]
    }
}

You use the method asDictionary to convert your objects to a dictionary so that you can serialize it into JSON.

Suppose you have a list of Balance objects.

You need to first convert each of those objects to dictionary using the method above, and then try to serialize the objects to JSON. Note that the list is now a list of [String: AnyObject] dictionaries, and not a list of Balance objects.

var resStr = ""

var list: [[String: AnyObject]] = [balance1.asDictionary(), balance2.asDictionary()] 
do {
   let data = try JSONSerialization.data(withJSONObject: list,  options: .prettyPrinted)
   resStr   = String.init(data: data, encoding: .utf8) ?? ""
} catch { fatalError("Error BALANCE HISTORY ")  }

For certain types, like the date field, you need to find some way to convert it to String, as the JSONSerializer is very picky. In this case I just used String interpolation, but the format may not be what you want it to be like.

If you want to convert back to a Balance object from JSON, first you need to get JSON into a dictionary, and then check for each field if it exists, and if so, construct your Balance object.

Supposing you have converted your JSON data into a dictionary in the variable named dict, you could do something like the following:

// supposing you have a single object in dict
var balance: Balance
balance.details = dict["details"]
balance.amount = dict["amount"]
balance.date = parseDate(dict["date"])

Supposing you have a function parseDate to parse the date from String into a Date object.

You can take a look here for converting String date into an object: Use SwiftyJSON to deserialize NSDate

Upvotes: 0

Duncan C
Duncan C

Reputation: 131398

You can only store a quite small list of data types to JSON.

@CoolPenguin offers one solution - a custom method that will convert your object to a JSON string.

I would advise against building JSON strings yourself.

Instead, I would suggest creating a toDictionary() method for your class, and an init(dictionary:) method to create a Balance object from a dictionary.

You can convert the dates to TimeIntervals since 1970 (the UNIX "epoch date".) That seems easier than converting them to date strings.

You can then map your array of Balance objects to an array of dictionaries, and convert that to JSON using normal JSONSerialization.

let mappedArray = balanceArray.map{$0.toDictionary()}

And then easily convert your array of dictionaries to JSON.

Upvotes: 1

CoolPenguin
CoolPenguin

Reputation: 1235

What about defining your class like this:

public class Balance: NSObject {
    var details: String
    var date:    Date
    var amount:  Double
    func toJSONString() -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        return "{details = \(self.details); date = \(dateFormatter.string(from: self.date)); amount = \(self.amount);}"
    }
}

Then you can create the full JSON string like this:

var fullJSONArray = [String]()
for balance in list {
    fullJSONArray.append(balance.toJSONString)
}
let fullJSONString = fullJSONArray.description

I hope this helps you out! Good luck and let me know if you have any further questions or issues!

Upvotes: 1

Related Questions