I have a class that inherits from AWSDynamoDBModel and adheres to AWSDynamoDBModeling protocol. Example:
class ProfileDatabaseModel : AWSDynamoDBModel, AWSDynamoDBModeling {
var userid: String
var firstName: String
var lastName: String
var someOtherStuff: [String: String] // IS THIS OK?
// assume some other details here, like initializations
class func dynamoDBTableName() -> String! {
return "atable"
}
class func hashKeyAttribute() -> String! {
return "userid"
}
}
With a class like this, I can perform the following few lines that update the DynamoDB with the data in an instantiation of this class:
var db = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
var model = ProfileDatabaseModel()
// fill in model data
let task: BFTask = db.save(model)
My question is: Can I have that dictionary in the object? Will that work with the object mapper? If not as a Swift dictionary, would it work as an NSDictionary? Or, do I need to translate it to a JSON format?
Currently, AWSDynamoDBObjectMapper supports:
NSNumber
NSString
NSData
NSArray of the above three datatypes
NSDictionary is not supported, but we are evaluating how we can support the map datatype in the coming releases.
Related
I'm trying to read a JSON file that I've created using the NewtonSoft JSON.Net parser in another (Windows) program. The JSON was created by the JSON.Net component when it serialized an array of objects. The sample JSON looks like the following (for this example I'm just showing two of the objects):
[{"MaxLength":23,"HasSpecialChars":false,"HasUpperCase":true,"Key":"firstOne"},
{"MaxLength":0,"HasSpecialChars":false,"HasUpperCase":false,"Key":"secondOne"}]
Notice that this is an array of objects in json.
Now I need some Swift code that will read this JSON in and write it out after values are altered in the program.
What I've Tried
I found this SO entry : Reading in a JSON File Using Swift
However, to get an array of objects, that entry uses separate structs that are Codable like the following:
struct ResponseData: Decodable {
var thisNameShowsUpInJson: [SiteKey]
}
That forces the outer array to have it's own name property in the json.
For example, the only way the code at that post works is if my JSON is altered to include an outer object with a name (SiteKey) like the following:
{"thisNameShowsUpInJson": [{"MaxLength":23,"HasSpecialChars":false,"HasUpperCase":true,"Key":"firstOne"},
{"MaxLength":0,"HasSpecialChars":false,"HasUpperCase":false,"Key":"secondOne"}]
}
However, that is not correct for the way that JSON.Net writes an array of objects to a file.
Here's my simple Swift class that I want to serialize and deserialize:
class SiteKey : Codable{
var Key : String
var MaxLength : Int
var HasSpecialChars : Bool
var HasUpperCase : Bool
init(key : String, maxLength : Int,
hasSpecialChars : Bool,
hasUpperCase : Bool){
Key = key;
MaxLength = maxLength;
HasSpecialChars = hasSpecialChars;
HasUpperCase = hasUpperCase;
}
}
I'd like to read the data from a named file and deserialize the data into objects.
Then, I'd like to serialize the in memory objects back out to a file like my example.
Imagine that you have an array of codable objects
var array = [SiteKey]()
then you can simply encode the entire array to Data using JSONEncoder
do {
let encoded = try JSONEncoder().encode(array)
} catch { print(error) }
To decode Data to your array of objects you can use JSONDecoder
do {
array = try JSONDecoder().decode([SiteKey].self, from: encoded)
} catch { print(error) }
My suggestions:
make your class struct instead, then you can remove hard-coded init since you get one for free
name variables with small capital letter and then use coding keys for renaming it while encoding/decoding
struct SiteKey : Codable {
var key : String
var maxLength : Int
var hasSpecialChars : Bool
var hasUpperCase : Bool
enum CodingKeys: String, CodingKey {
case key = "Key"
case maxLength = "MaxLength"
case hasSpecialChars = "HasSpecialChars"
case hasUpperCase = "HasUpperCase"
}
}
I discovered the code I need to use in Swift which allows me to read and write the JSON (array of objects) that is output by JSON.Net.
I've added two methods to my SiteKey object :
func loadJson(filename fileName: String) -> [SiteKey]
func writeJson(filename fileName: String, allSiteKeys : [SiteKey])
The first function takes a string that points to a json file and returns the array of SiteKeys that is in the file.
The second function takes a filename and the array of SiteKey objects and writes them to the file.
Here's the altered SiteKey class with the added functions.
class SiteKey : Codable{
var Key : String
var MaxLength : Int
var HasSpecialChars : Bool
var HasUpperCase : Bool
init(key : String, maxLength : Int,
hasSpecialChars : Bool,
hasUpperCase : Bool){
Key = key;
MaxLength = maxLength;
HasSpecialChars = hasSpecialChars;
HasUpperCase = hasUpperCase;
}
func loadJson(filename fileName: String) -> [SiteKey]? {
if let url = Bundle.main.url(forAuxiliaryExecutable: fileName) {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let allKeys = try decoder.decode([SiteKey].self, from: data)
return allKeys
} catch {
print("error:\(error)")
}
}
return nil
}
func writeJson(filename fileName: String, allSiteKeys : [SiteKey]){
let Data = try? JSONEncoder().encode(allSiteKeys)
let pathAsURL = URL(fileURLWithPath: fileName)
do {
try Data?.write(to: pathAsURL)
}
catch {
print("Failed to write JSON data: \(error.localizedDescription)")
}
}
}
Here's the usage:
let newSiteKey = siteKey.loadJson(filename: "/Users/fakeUser/Documents/Dev/all.json")
When the loadJson method returns the newSiteKey will contain an array of SiteKey class objects that can be iterated through.
In Swift 3: Imagine you want your models to be value types (struct) through out your app. But you would also like persistence of said models using Core Data/Realm, which requires you to create classes. Then you can convert your structs to classes and vice verse using JSON (which would require structs and classes to both support JSON deserialization and serialization).
Wouldn't it be neat if you don't have to write JSON deserialization (and analogously for serialization, but I'm focusing on deserialization here) in two places, but use put deserialization in a protocol, that both your struct and class uses.
Using structs we want our JSON model to have immutable fields, thus all properites being let constants. But using a protocol implementation of the deserialization does not allow for this AFAIK.
The code example below works, but it is ugly, because of all unwanted requirements (UR) marked in comments in the code.
struct JSON {
let json: [String: Any]
func value<Value>(_ key: String) throws -> Value {
guard let value = json[key] as? Value else { throw NSError() }
return value
}
}
protocol JSONDeserializable {
init(json: JSON) throws
}
protocol UserModel: JSONDeserializable {
var username: String { get set } // Unwanted requirement (UR) #1: property needs "set" so that it can be initialized within protocol
init() // UR2: needs empty init, because of default implementation of `init(json: JSON)` in `extension UserModel`
}
extension UserModel {
init(json: JSON) throws {
self.init() // UR3: Needs to call this otherwise compilation error: `'self' used before chaining to another self.init requirement`
username = try json.value("username")
}
}
struct UserStruct: UserModel {
// UR4: property cannot be `let`, beause of `set` in protocol.
var username: String = "" // UR5: Property have to have default value because of it being a empty init
init() {}
}
final class UserClass: NSObject, UserModel {
// UR6: analogue with UR4
var username: String = "" // UR7: analogue with UR5
}
let json: JSON = JSON(json: ["username": "Sajjon"])
let u1 = try UserStruct(json: json)
let u2 = try UserClass(json: json)
print(u1.username) // prints "Sajjon"
print(u2.username) // prints "Sajjon"
Is there another way of achieving this, with a lower amount of unwanted requirements? Or an optimal solution with zero UR? 🙄
Thanks to what #hamish pointed out, the best solution would be (where struct JSON and protocol JSONDeserializable remains the same as in the question). This is not a perfect solution since you have to implement the initializer of the class. The neat part is that you don't have to implement any initializer for the struct, since it has one implicitly.
protocol UserModel: JSONDeserializable {
var username: String { get }
var firstname: String { get }
var country: String { get }
init(
username: String,
firstname: String,
country: String
)
}
extension UserModel {
init(json: JSON) throws {
self.init(
username: try json.value("username"),
firstname: try json.value("firstname"),
country: try json.value("country")
)
}
}
struct UserStruct: UserModel {
let username: String
let firstname: String
let country: String
// struct has default initializer
}
final class UserClass: UserModel {
let username: String
let firstname: String
let country: String
init(
username: String,
firstname: String,
country: String
) {
self.username = username
self.firstname = firstname
self.country = country
}
}
let json: JSON = JSON(json: [
"username": "Sajjon",
"firstname": "Alexander",
"country": "Sweden"
])
let u1 = try UserStruct(json: json)
let u2 = try UserClass(json: json)
print(u1.username) // prints "Sajjon"
print(u2.username) // prints "Sajjon"
I have this problem
I'm using Swift 2.0 in my project for iOS 9. I've created an object like this:
public class element_x: NSObject{
var name: String!
var description_element: String!
}
So, In a method I declare two NSMutableArray: 1) for all data and 2) for filter data, like this:
var original = NSMutableArray()
var filtered = NSMutableArray()
And during the process I populate this NSMutableArray like this:
let custom_object = element_x()
self.original.addObject(custom_object);
My question is: how can I filter original array by name value and saved in filtered array?
You don't have to use NSMutableArray. The native Array type in Swift is very capable. You can declare it mutable with var (equivalent to NSMutableArray) or constant with let (same as NSArray):
public class element_x: NSObject{
var name: String!
var description_element: String!
}
// Declare an array containing elements of element_x type
var original = [element_x]()
var filtered = [elememt_x]()
let custom_object = element_x()
self.original.append(custom_object)
// Find all elements with name == "david"
self.filtered = self.original.filter { $0.name == "david" }
I have a struct named Jarand I would like to save an array of them to NSUserDefaults. Here is the jar struct code:
struct Jar {
let name: String
let amount: Int
init(name: String, amount: Int){
self.name = name
self.amount = amount
}
}
I belive that I will need to convert this to an NSObject to be able to save it. (Because you can't save a struct directly to NSUserDefaults). My questions are:
How do I convert an array of structs to an NSObject? and How to convert an NSObject back at an array of structs.
This solution is inspired by #Duncan C. I wrote it more familiar way as we do in case Custom Class encoding and decoding.
public struct IRDriver {
public var name: String?
public var amount: Int?
public init() {
}
// Decode
public init(dictionary: Dictionary<String, AnyObject>){
name = dictionary["name"] as? String
amount = dictionary["amount"] as? Int
}
// Encode
public func encode() -> Dictionary<String, AnyObject> {
var dictionary : Dictionary = Dictionary<String, AnyObject>()
dictionary["name"] = name
dictionary["amount"] = amount
return dictionary
}
}
For saving to user defaults you have a couple of options: Have the object conform to NSCoding, or implement methods that convert it to/from an NSDictionary, and save that.
Something like this:
func dictionaryFromJar() -> NSDictionary
{
let dictionary: [AnyObject: AnyObject] = ["name": name, "amount": amount]
return dictionary
}
I think the automatic bridging between Swift dictionaries and NSDictionary would work here, but I'm not positive. My swift is getting a little rusty. :(
I can't get a value out of this computed property (userList)...is my init set up correctly? I'm trying to build a list that will populate the rows in my table view. (*Edit: I've edited my question with a saveData function, but "cannot invoke setObject" with an argument list of type ([String : User], forKey: String)" –
import Foundation
class DataManager {
static let sharedInstance = DataManager()
var users = [String : User]()
init() {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let var userFromDefaults = userDefaults.objectForKey("userKey") as? [String : User] {
users = userFromDefaults
}
else {
// add default values later
}
}
func saveData() {
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(users, forKey: "userKey")
}
var userList: [String] {
var list: [String] = []
for name in users.keys {
list.append(name)
}
list.sort(<)
return list
}
My viewcontroller:
import UIKit
class NameViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var users: [String] = DataManager.sharedInstance.userList
User Struct with a method to convert into json:
import Foundation
struct User {
var name = ""
var stores: [Store] = []
init?(json: [String: AnyObject]) {
if let name = json["name"] as? String,
storesJSON = json["stores"] as? [[String: AnyObject]]
{
self.name = name
self.stores = storesJSON.map { Store(json: $0)! }
} else {
return nil
}
}
init() { }
func toJSON() -> [String: AnyObject] {
return [
"name": name,
"stores": stores.map { $0.toJSON() }
]
}
}
You can grab all keys/Values using (from apple documentation)
dictionaryRepresentation() Returns a dictionary that contains a union
of all key-value pairs in the domains in the search list.
Using this you can loop all the elements in the dictionary
for element in NSUserDefaults.standardUserDefaults().dictionaryRepresentation() {
println(element)
//Do what you need to do here
}
You can also retrieve just the keys:
println(NSUserDefaults.standardUserDefaults().dictionaryRepresentation().keys.array);
or just the values
println(NSUserDefaults.standardUserDefaults().dictionaryRepresentation().values.array);
As Matt pointed out in his comment, you need to save contents to NSUserDefaults before you can read them.
Something else that will prevent your code from working:
NSUserDefaults will only save "property list" objects (NSString, NSData, NSDate, NSNumber, NSArray, or NSDictionary objects or their Swift equivalents.)
If you try to save anything else, or a collection containing anything but those data types, the save fails and you get nil when you try to read it back. Your code shows you trying to save a dictionary where the value for each key is an object of type "User". That's going to fail since "User" is not one of the short list of "property list" object types.
This is not a good user of NSUserDefaults, which is meant to store preferences and other small bits of data between app launches. See the documentation on NSUserDefaults.
You should be managing this dictionary of users in memory when you are displaying data, and if you want to persist it to disk between app launches you should use CoreData or make your User class conform to NSCoding and read/write it with archiving. Here's a nice tutorial.