I need to save a list of images as NSData in Realm. I tried using Realm optional but realmOptional<NSdata> can't be used because realmOptional does not conform to type NSDate.
Is there a way to do it?
Edit: Basically all I want is to be able to store a list of NSData but optional
something like:
#objc dynamic var photos: List<NSData>?
Solution for optional List types when using Decodable
In my case I needed an optional List because I'm decoding json into Realm objects, and it's possible that the property might not exist in the json data. The typical workaround is to manually decode and use decodeIfPresent(). This isn't ideal because it requires boilerplate code in the model:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
// etc
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.vehicles = try container.decodeIfPresent(List<Vehicle>.self, forKey: .vehicles) ?? List<Vehicle>()
self.name = try container.decode(String.self, forKey: .name)
// etc
}
}
However, we can avoid the boilerplate by extending KeyedDecodingContainer with an overload for List types. This will force all lists to be decoded using decodeIfPresent():
Realm v5:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
//etc
}
class Vehicle: Object, Decodable {
var drivers = List<Driver>()
var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any List<> is decoded
func decode<T: Decodable>(_ type: List<T>.Type, forKey key: Key) throws -> List<T> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? List<T>()
}
}
Realm v10+:
class Driver: Object, Decodable {
#Persisted var vehicles: List<Vehicle>
#Persisted var name: String = ""
// etc
}
class Vehicle: Object, Decodable {
#Persisted var drivers: List<Driver>
#Persisted var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any #Persisted List<> is decoded
func decode<T: Decodable>(_ type: Persisted<List<T>>.Type, forKey key: Key) throws -> Persisted<List<T>> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? Persisted<List<T>>(wrappedValue: List<T>())
}
}
Edit: Clarified code examples, fixed typo
The List cannot be optional, but the Objects in the list can be optional, you have to declare it like:
#Persisted var value: List<Type?>
Here is the link with the Supported data types
https://www.mongodb.com/docs/realm/sdk/swift/data-types/supported-property-types/
according to https://realm.io/docs/swift/latest/#property-cheatsheet you can not define optional lists in realm
Related
I have JSON that looks like this, which returns a list of Posts:
[
{
"id" : 1,
"message": "Hello"
"urls" : {
"png" : "https://example.com/image.png",
"jpg" : "https://example.com/image.jpg",
"gif" : "https://example.com/image.gif"
}
}
]
As you can see, I need to make two classes. One for the parent object (Post), and one for the object "urls" (PostUrls).
I've done that like so:
class Post: Object, Decodable {
#objc dynamic var id = 0
#objc dynamic var message: String? = nil
#objc dynamic var urls: PostUrls? = nil
override static func primaryKey() -> String? {
return "id"
}
private enum PostCodingKeys: String, CodingKey {
case id
case message
case urls
}
convenience init(id: Int, message: String, urls: PostUrls) {
self.init()
self.id = id
self.message = message
self.urls = urls
}
convenience required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: PostCodingKeys.self)
let id = try container.decode(Int.self, forKey: .id)
let message = try container.decode(String.self, forKey: .message)
let urls = try container.decode(PostUrls.self, forKey: .urls)
self.init(id: id, message: message, urls: urls)
}
required init() {
super.init()
}
}
And
#objcMembers class PostUrls: Object, Decodable {
dynamic var png: String? = nil
dynamic var jpg: String? = nil
dynamic var gif: String? = nil
private enum PostUrlsCodingKeys: String, CodingKey {
case png
case jpg
case gif
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: PostUrlsCodingKeys.self)
png = try container.decodeIfPresent(String.self, forKey: .png)
jpg = try container.decodeIfPresent(String.self, forKey: .jpg)
gif = try container.decodeIfPresent(String.self, forKey: .gif)
super.init()
}
required init() {
super.init()
}
}
But, the problem is that I have no relationship between Post and PostUrls, since there is no primary key to connect the two. Further, this also means that I currently won't be able to control duplicates inside the PostUrls table.
So my question is: how can I create a relationship between the two tables, and prevent duplicates in the PostUrls table?
In this case, you do have a relationship between those objects. Object Post contains object PostUrls. Realm does not require a primary key to have this kind of relationship, because it creates a primary key behind the scenes. So it uses it, even though you can't access it.
To manually set a primaryKey you have to override a func called primaryKey()
#objcMembers class DBFilterModel: Object {
// MARK: Properties
dynamic var id: Int = 0
override public static func primaryKey() -> String? {
return "id"
}
}
This way you tell to realm that you want this property to be used as a Unique Key.
To prevent duplicating them, there are 2 ways. First - try to find an object with that id already existing in your database, if it exists - don't create it.
Second - by adding conflicts handlers to Realm's save methods. You can set that objects with same ID's will be just modified, but not duplicated. Or you could say that you want to throw an error when you try to insert a duplicated object.
realm.add(objects, update: update ? .modified : .error)
The question has two questions within
How do you create a relationship between the two 'tables'
prevent duplicates
Let me address 1)
Start with a class to hold the image type (.jpg etc) and then the url
class ImageUrlClass: Object {
#objc dynamic var imageType = ""
#objc dynamic var imageUrl = ""
}
and then the main class which handles decoding
class Post: Object, Decodable {
#objc dynamic var id: Int = 0
#objc dynamic var message: String = ""
let urlList = List<ImageUrlClass>()
...edited for brevity
convenience init(id: Int, message: String, urls: [String: String]) {
self.init()
self.id = id
self.message = message
//create a ImageUrlClass from each dictionary entry
for url in urls {
let key = url.key
let value = url.value
let aUrl = ImageUrlClass(value: ["imageType": key, "imageUrl": value])
self.urlList.append(aUrl)
}
}
convenience required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: PostCodingKeys.self)
let id = try container.decode(Int.self, forKey: .id)
let message = try container.decode(String.self, forKey: .message)
let urls = try container.decode([String: String].self, forKey: .urls)
self.init(id: id, message: message, urls: urls)
}
}
The above will create a Post object with a List property that contains the image types and urls (a List behaves very similar to an array)
You could further this by adding a LinkingObjects property to the ImageUrlClass which would automagically create an inverse relationship to the Post object when objects are added to the List. Not sure if you need that but it's available.
You can this do this to print out the post properties
let decoder = JSONDecoder()
let post = try! decoder.decode(Post.self, from: encodedData)
print(post.id)
print(post.message)
for url in post.urlList {
let a = url.imageType
let b = url.imageUrl
print(a,b)
}
which would results in an output like this
1
Hello
jpg https://example.com/image.jpg
png https://example.com/image.png
gif https://example.com/image.gif
I am trying to parse some JSON in Swift using JSONDecoder where the JSON occasionally has null values. I would like to put in a default instead.
The following allows me to handle it but the nulls cause problems later.
struct Book: Codable {
let title : String
let author: String?
}
Is there a way to do something like (following does not compile), perhaps using an initializer?:
struct Book: Codable {
let title : String
let author: String ?? "unknown"
}
Thanks for any suggestions
This could be address by manually decoding as described here.
The other way to go would be to have the stored properties reflect the data exactly, and then have a computed var for the case of providing a non-optional value.
struct Book: Codable {
let title : String
let author: String?
var displayAuthor: String {
return author ?? "unknown"
}
}
The other reason this might be appealing is it preserves the optional value should you need to check if the value exists at all in the future.
You can achieve this using the custom init(decoder:) method definition. Use the decodeIfPresent API and give the property your desired default value if the try fails. Or you can use the computed property method mentioned by #dktaylor. Here the code you need:
struct Book {
let title : String
let author: String
enum CodingKeys: String, CodingKey {
case title, author
}
}
extension Book: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
author = try container.decodeIfPresent(String.self, forKey: .author) ?? "unknown"
}
}
You can also achieve this with a property wrapper:
#propertyWrapper
struct OnNil<T> {
let value: T
init(_ value: T) {
self.value = value
}
private var _wrappedValue: T?
var wrappedValue: T! {
get { _wrappedValue ?? value }
set { _wrappedValue = newValue }
}
}
struct SomeStruct {
let title : String
#OnNil("unknown")
let author: String!
}
The benefits of using a property wrapper like this is that you don't have to soil your object with utility methods, and you don't have to fiddle with the decode function. The downside obviously is that the syntax looks kind of odd, you have to make some variables implicitly unwrapped optional, and it makes your code slightly harder to debug due to the nature of property wrappers.
My app uses a codable struct like so:
public struct UserProfile: Codable {
var name: String = ""
var completedGame: Bool = false
var levelScores: [LevelScore] = []
}
A JSONEncoder() has been used to save an encoded array of UserProfiles to user defaults.
In an upcoming update, I'd like to add a new property to this UserProfile struct. Is this possible in some way?
or would I need to create a new Codable struct that has the same properties plus one new property, and then copy all the values over to the new struct and then start using that new struct in place of anywhere that the UserProfile struct was previously used?
If I simply add a new property to the struct, then I won't be able to load the previous encoded array of UserProfiles as the struct will no longer have matching properties. When I get to this code for loading the saved users:
if let savedUsers = UserDefaults.standard.object(forKey: "SavedUsers") as? Data {
let decoder = JSONDecoder()
if let loadedUsers = try? decoder.decode([UserProfile].self, from: savedUsers) {
loadedUsers doesn't decode if the properties that UserProfile had when they were encoded and saved do not contain all the current properties of the UserProfile struct.
Any suggestions for updating the saved struct properties? Or must I go the long way around and re-create since I didn't already plan ahead for this property to be included previously?
Thanks for any help!
As mentioned in the comments you can make the new property optional and then decoding will work for old data.
var newProperty: Int?
Another option is to apply a default value during the decoding if the property is missing.
This can be done by implementing init(from:) in your struct
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
completedGame = try container.decode(Bool.self, forKey: .completedGame)
do {
newProperty = try container.decode(Int.self, forKey: .newProperty)
} catch {
newProperty = -1
}
levelScores = try container.decode([LevelScore].self, forKey: .levelScores)
}
This requires that you define a CodingKey enum
enum CodingKeys: String, CodingKey {
case name, completedGame, newProperty, levelScores
}
A third option if you don't want it to be optional when used in the code is to wrap it in a computed non-optional property, again a default value is used. Here _newProperty will be used in the stored json but newProperty is used in the code
private var _newProperty: Int?
var newProperty: Int {
get {
_newProperty ?? -1
}
set {
_newProperty = newValue
}
}
Swift 4 added the new Codable protocol. When I use JSONDecoder it seems to require all the non-optional properties of my Codable class to have keys in the JSON or it throws an error.
Making every property of my class optional seems like an unnecessary hassle since what I really want is to use the value in the json or a default value. (I don't want the property to be nil.)
Is there a way to do this?
class MyCodable: Codable {
var name: String = "Default Appleseed"
}
func load(input: String) {
do {
if let data = input.data(using: .utf8) {
let result = try JSONDecoder().decode(MyCodable.self, from: data)
print("name: \(result.name)")
}
} catch {
print("error: \(error)")
// `Error message: "Key not found when expecting non-optional type
// String for coding key \"name\""`
}
}
let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional
You can implement the init(from decoder: Decoder) method in your type instead of using the default implementation:
class MyCodable: Codable {
var name: String = "Default Appleseed"
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let name = try container.decodeIfPresent(String.self, forKey: .name) {
self.name = name
}
}
}
You can also make name a constant property (if you want to):
class MyCodable: Codable {
let name: String
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let name = try container.decodeIfPresent(String.self, forKey: .name) {
self.name = name
} else {
self.name = "Default Appleseed"
}
}
}
or
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"
}
Re your comment: With a custom extension
extension KeyedDecodingContainer {
func decodeWrapper<T>(key: K, defaultValue: T) throws -> T
where T : Decodable {
return try decodeIfPresent(T.self, forKey: key) ?? defaultValue
}
}
you could implement the init method as
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeWrapper(key: .name, defaultValue: "Default Appleseed")
}
but that is not much shorter than
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"
You can use a computed property that defaults to the desired value if the JSON key is not found.
class MyCodable: Decodable {
var name: String { return _name ?? "Default Appleseed" }
var age: Int?
// this is the property that gets actually decoded/encoded
private var _name: String?
enum CodingKeys: String, CodingKey {
case _name = "name"
case age
}
}
If you want to have the property read-write, you can also implement the setter:
var name: String {
get { _name ?? "Default Appleseed" }
set { _name = newValue }
}
This adds a little extra verbosity as you'll need to declare another property, and will require adding the CodingKeys enum (if not already there). The advantage is that you don't need to write custom decoding/encoding code, which can become tedious at some point.
Note that this solution only works if the value for the JSON key either holds a string or is not present. If the JSON might have the value under another form (e.g. it's an int), then you can try this solution.
Approach that I prefer is using so called DTOs - data transfer object.
It is a struct, that conforms to Codable and represents the desired object.
struct MyClassDTO: Codable {
let items: [String]?
let otherVar: Int?
}
Then you simply init the object that you want to use in the app with that DTO.
class MyClass {
let items: [String]
var otherVar = 3
init(_ dto: MyClassDTO) {
items = dto.items ?? [String]()
otherVar = dto.otherVar ?? 3
}
var dto: MyClassDTO {
return MyClassDTO(items: items, otherVar: otherVar)
}
}
This approach is also good since you can rename and change final object however you wish to.
It is clear and requires less code than manual decoding.
Moreover, with this approach you can separate networking layer from other app.
You can implement.
struct Source : Codable {
let id : String?
let name : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
name = try values.decodeIfPresent(String.self, forKey: .name)
}
}
I came across this question looking for the exact same thing. The answers I found were not very satisfying even though I was afraid that the solutions here would be the only option.
In my case, creating a custom decoder would require a ton of boilerplate that would be hard to maintain so I kept searching for other answers.
I ran into this article that shows an interesting way to overcome this in simple cases using a #propertyWrapper. The most important thing for me, was that it was reusable and required minimal refactoring of existing code.
The article assumes a case where you'd want a missing boolean property to default to false without failing but also shows other different variants.
You can read it in more detail but I'll show what I did for my use case.
In my case, I had an array that I wanted to be initialized as empty if the key was missing.
So, I declared the following #propertyWrapper and additional extensions:
#propertyWrapper
struct DefaultEmptyArray<T:Codable> {
var wrappedValue: [T] = []
}
//codable extension to encode/decode the wrapped value
extension DefaultEmptyArray: Codable {
func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode([T].self)
}
}
extension KeyedDecodingContainer {
func decode<T:Decodable>(_ type: DefaultEmptyArray<T>.Type,
forKey key: Key) throws -> DefaultEmptyArray<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
The advantage of this method is that you can easily overcome the issue in existing code by simply adding the #propertyWrapper to the property. In my case:
#DefaultEmptyArray var items: [String] = []
Hope this helps someone dealing with the same issue.
UPDATE:
After posting this answer while continuing to look into the matter I found this other article but most importantly the respective library that contains some common easy to use #propertyWrappers for these kind of cases:
https://github.com/marksands/BetterCodable
If you don't want to implement your encoding and decoding methods, there is somewhat dirty solution around default values.
You can declare your new field as implicitly unwrapped optional and check if it's nil after decoding and set a default value.
I tested this only with PropertyListEncoder, but I think JSONDecoder works the same way.
If you think that writing your own version of init(from decoder: Decoder) is overwhelming, I would advice you to implement a method which will check the input before sending it to decoder. That way you'll have a place where you can check for fields absence and set your own default values.
For example:
final class CodableModel: Codable
{
static func customDecode(_ obj: [String: Any]) -> CodableModel?
{
var validatedDict = obj
let someField = validatedDict[CodingKeys.someField.stringValue] ?? false
validatedDict[CodingKeys.someField.stringValue] = someField
guard
let data = try? JSONSerialization.data(withJSONObject: validatedDict, options: .prettyPrinted),
let model = try? CodableModel.decoder.decode(CodableModel.self, from: data) else {
return nil
}
return model
}
//your coding keys, properties, etc.
}
And in order to init an object from json, instead of:
do {
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
let model = try CodableModel.decoder.decode(CodableModel.self, from: data)
} catch {
assertionFailure(error.localizedDescription)
}
Init will look like this:
if let vuvVideoFile = PublicVideoFile.customDecode($0) {
videos.append(vuvVideoFile)
}
In this particular situation I prefer to deal with optionals but if you have a different opinion, you can make your customDecode(:) method throwable
I am having problems in decoding JSON response using Swift 4 Decoding Functionality.
I have main construct and it has one inner construct var hr_employees: [Employee]? = []. The problem is JSON not mapping for 'var hr_employees: [Employee]? = [].
I am getting correct values forthe three root values response_status,access_level,session_token.
////////////Code for Struct////////////////////////
struct EmployeeData: Codable {
var response_status:Int=0
var access_level:Int=0
var session_token:String=""
var hr_employees: [Employee]? = []
}
private enum CodingKeys: String, CodingKey {
case response_status="response_status"
case access_level="access_level"
case session_token="session_token"
case hr_employees="hr_employees"
}
init() {
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
response_status = try values.decode(Int.self, forKey: .response_status)
do{
session_token = try values.decode(String.self, forKey: .session_token)
}catch {
print( "No value associated with key title (\"session_token\").")
}
do{
access_level = try values.decode(Int.self, forKey: .access_level)
}
catch {
print( "No value associated with key access_level ")
}
}
/////////////////Inner Struct///////////////////////
struct Employee: Codable {
var userId:Int=0
var nameFirst:String=""
var nameLast:String=""
var position:String=""
var company:String=""
var supervisor:String=""
var assistant:String=""
var phone:String=""
var email:String=""
var address:String=""
var gender:String=""
var age:Int=0
var nationality:String=""
var firstLanguage:String=""
var inFieldOfView:String = "0"
var photo:String="user-default"
var status:String="3"
}
////////////Following is the JSON//////////////////////
{
"response_status":1
,"access_level":2
,"hr_employees":[
{
"user_id":4226
,"name_last":"Sampe"
,"name_first":"Frederica"
,"position":"Systems Maint"
,"phone":"123456"
,"email":"omega#demo.mobile"
,"address":"00100 Helsinki 1"
,"age":67
,"company":"Omega Enterprise"
}
,{
"user_id":5656
,"name_last":"Aalto"
,"name_first":"Antero"
,"position":"Programming Methodology and Languages Researcher"
,"supervisor":"Mayo Georgia"
,"phone":"123456"
,"email":"omega#demo.mobile"
,"address":"00100 Finland "
,"age":51
,"company":"Omega Fire Related Equipment"
}
]
}
One problem is that what is in the JSON does not match your definition of Employee. For example nameFirst is not present and name_first is.
Another is that you have a custom implementation of init(from:), and it never fetches the hr_employees value!
Quite a few things for you to improve on:
Your Structs can be improved to harness automation capability of the Codable protocol.
You need to understand why you're using a CodingKeys enum
and in your case... also where best to have it (hint: inside the Struct itself)
You need to know which parameters need to be optional and why
this depends on your json structure ofcourse
If the parameters are to have a default value then there's a whole different process you need to follow; like having your own init(from:Decoder)
which you have to a certain extent but doesn't really handle everything in it's current state
Based on your given JSON example, you can simply do the following.
However... do note that this is not designed to provide default values. i.e. If a key is missing in the json, like status for example, then the parameter status in your Employee struct will be nil rather than a default value of "3".
struct EmployeeData: Codable {
var responseStatus: Int
var accessLevel: Int
/*
sessionToken is optional because as per your JSON
it seems it not always available
*/
var sessionToken: String?
var hrEmployees: [Employee]
/*
CodingKeys is inside the struct
It's used if the JSON key names are different than
the ones you plan to use.
i.e. JSON has keys in snake_case but we want camelCase
*/
enum CodingKeys: String, CodingKey {
case responseStatus = "response_status"
case accessLevel = "access_level"
case sessionToken = "session_token"
case hrEmployees = "hr_employees"
}
}
struct Employee: Codable {
var userId: Int
var nameFirst: String
var nameLast: String
var position: String
var company: String
var supervisor: String?
var assistant: String?
var phone: String
var email: String
var address: String
var gender: String?
var age: Int
var nationality: String?
var firstLanguage: String?
var inFieldOfView: String?
var photo: String?
var status: String?
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case nameFirst = "name_first"
case nameLast = "name_last"
case firstLanguage = "first_language"
case inFieldOfView = "in_field_of_view"
/*
Keys names that are same in json as well as in your
model need not have a raw string value
but must be defined if it's to be encoded/decoded
from the json else it can be omitted and a default
value will be required which won't affect the encoding
or decoding process
*/
case position
case company
case supervisor
case assistant
case phone
case email
case address
case gender
case age
case nationality
case photo
case status
}
}
Check:
do {
let employeeData = try JSONDecoder().decode(EmployeeData.self,
from: jsonAsData)
print(employeeData)
}
catch {
/*
If it comes here then debug, it's most probably nil keys
meaning you need more optional parameters in your struct
*/
print(error)
}
If you want default values in your Struct and the above example is a dealbreaker for you then check the following answer:
https://stackoverflow.com/a/44575580/2857130