Decoding empty string into nil with Codable - ios

normal case:
{
"maintenance": true
}
{
"maintenance": false
}
If there is no maintenance station then it will become empty string
{
"maintenance": ""
}
i want to have nil if maintenance is empty string in json
struct Demo: Codable {
var maintenance: Bool?
}
Is there a good way to do it?

What you need is to try to decode your Bool, catch the error, try to decode a string and check if it is an empty string otherwise throw the error. This will make sure you don't discard any decoding error even if it is a string but not empty:
struct Demo: Codable {
var maintenance: Bool?
}
struct Root: Codable {
var maintenance: Bool?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
maintenance = try container.decode(Bool.self, forKey: .maintenance)
} catch {
guard try container.decode(String.self, forKey: .maintenance) == "" else {
throw error
}
maintenance = nil
}
}
}
Playground testing:
let json1 = """
{
"maintenance": true
}
"""
let json2 = """
{
"maintenance": false
}
"""
let json3 = """
{
"maintenance": ""
}
"""
let json4 = """
{
"maintenance": "false"
}
"""
do {
let root1 = try JSONDecoder().decode(Root.self, from: Data(json1.utf8))
print("root1", root1)
} catch {
print(error)
}
do {
let root2 = try JSONDecoder().decode(Root.self, from: Data(json2.utf8))
print("root2", root2)
} catch {
print(error)
}
do {
let root3 = try JSONDecoder().decode(Root.self, from: Data(json3.utf8))
print("root3", root3)
} catch {
print(error)
}
do {
let root4 = try JSONDecoder().decode(Root.self, from: Data(json4.utf8))
print("root4", root4)
} catch {
print(error)
}
This will print
root1 Root(maintenance: Optional(true))
root2 Root(maintenance: Optional(false))
root3 Root(maintenance: nil)
typeMismatch(Swift.Bool, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "maintenance", intValue: nil)], debugDescription: "Expected to decode Bool but found a string/data instead.", underlyingError: nil))

You can try
struct Root: Codable {
var maintenance: Bool?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.maintenance = try container.decode(Bool.self, forKey: .maintenance)
}
catch {
}
}
}

In most cases, I'd probably use SeaSpell or Sh_Khan's solutions. Bool? is usually not the right type, but in this case it seems precisely what you mean (you seem to want to keep track of whether the value was set or not, rather than defaulting to something). But those approaches do require a custom decoder for the whole type which might be inconvenient. Another approach would be to define a new type just for this 3-way value:
enum Maintenance {
case `true`, `false`, unset
}
(Maybe "enabled" and "disabled" would be better here than taking over the true and false keywords. But just showing what Swift allows.)
You can then decode this in a very strict way, checking for true, false, or "" and rejecting anything else ("false" for example).
extension Maintenance: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(Bool.self) {
self = value ? .true : .false
} else if let value = try? container.decode(String.self), value.isEmpty {
self = .unset
} else {
throw DecodingError.dataCorruptedError(in: container,
debugDescription: "Unable to decode maintenance")
}
}
}
Depending on how the rest of your code work, this three-way enum may be more convenient or less convenient than an Optional Bool, but it's another option.
With this, you don't need anything special in Demo:
struct Demo: Decodable {
var maintenance: Maintenance
}
An important distinction is that maintenance here is not optional. It is required. There are just three acceptable values. Even if using Bool? you should think hard about whether there is a difference between "" and missing.

You really only have a couple of decent options that I know of.
do codable init yourself and loose the free one.
struct Root: Codable {
var maintenance: Bool?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let value = (try? container.decode(Bool.self, forKey: .maintenance) ?? "")
self.maintenance = (value.isEmpty() || value == "false" ) ? nil : true
}
}
For obvious reasons this may not be ideal, especially if you have a lot of other variables to decode. The other option is to use a getter and add a variable to store the string optional.
Calculated var
private var _maintenance: String?
var maintenance: Bool {
get {
((_maintenance ?? "").isEmpty || _maintenance == "false") ? false : true
}
}
This solution is more ideal because you only need to change coding keys and add a var.

Bool? is a very bad idea unless you know exactly what you are doing, because you have three possible values: true, false and nil.
You can’t use it directly in an if statement. You can compare b == true, b == false or b == nil and I expect that b == false will not produce the result you expect.
You want to use a three-value type for two possible values, which is asking for trouble.

I'd get the API changed because that's just bad design to have a field with two different types. It'd be much more sensible to have it as something like
maintenance: { active: true }
And for the case where there's no station, either set it as null or remove the field

Related

Swift model not reading data from Firestore

I have a base model called Requirements and another more specific model called AccountRequirements.
When I try to read the currentDeadline property, if i use Requirements it works fine. If I use AccountRequirements it comes out as nil.
I do not understand why. I'm guessing it has to do somehow with the class. I always use struct in my models but since I can not inherit from a struct I'm using class here.
class Requirements: Codable {
var commonProperty: String
// works
var currentDeadline: Int?
enum CodingKeys: String, CodingKey {
case commonProperty = "common_property"
case currentDeadline = "current_deadline"
}
}
class AccountRequirements: Requirements {
// doesnt work
var currentDeadline: Int?
enum CodingKeys: String, CodingKey {
case currentDeadline = "current_deadline"
}
}
I decode data like this:
documentReference.addSnapshotListener { [self] documentSnapshot, error in
guard let document = documentSnapshot else {
self.error = error!.localizedDescription
return
}
self.user = try? document.data(as: Requirements.self)
}
If you want to decode it as the subclass then you need to give that class and not the superclass to document.data(as:). You also need to implement init(from:) for the subclass to decode it properly
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
currentDeadline = try container.decodeIfPresent(Int.self, forKey: .currentDeadline)
try super.init(from: decoder)
}
Below is an example with a hardcoded json value
let data = """
{ "common_property": "Some text",
"current_deadline": 42
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(Requirements.self, from: data)
print(type(of: result), result.commonProperty)
let result2 = try JSONDecoder().decode(AccountRequirements.self, from: data)
print(type(of: result2), result2.commonProperty, result2.currentDeadline ?? "")
} catch {
print(error)
}
Requirements Some text
AccountRequirements Some text 42

How to Decode selected keys manually and rest with the automatic decoding with swift Decodable?

Here is the code I am using,
struct CreatePostResponseModel : Codable{
var transcodeId:String?
var id:String = ""
enum TopLevelCodingKeys: String, CodingKey {
case _transcode = "_transcode"
case _transcoder = "_transcoder"
}
enum CodingKeys:String, CodingKey{
case id = "_id"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TopLevelCodingKeys.self)
if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcode) {
self.transcodeId = transcodeId
}else if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcoder) {
self.transcodeId = transcodeId
}
}
}
Here, transcodeId is decided by either _transcode or _transcoder.
But I want id and rest of the keys (not included here) to be automatically decoded. How can I do it ?
You need to manually parse all the keys once you implement init(from:) in the Codable type.
struct CreatePostResponseModel: Decodable {
var transcodeId: String?
var id: String
enum CodingKeys:String, CodingKey{
case id, transcode, transcoder
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcode) {
self.transcodeId = transcodeId
} else if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcoder) {
self.transcodeId = transcodeId
}
}
}
In the above code,
In case you only want to decode the JSON, there is no need to use Codable. Using Decodable is enough.
Using multiple enums for CodingKey seems unnecessary here. You can use a single enum CodingKeys.
If the property name and the key name is an exact match, there is no need to explicitly specify the rawValue of that case in enum CodingKeys. So, there is no requirement of "_transcode" and "_transcoder" rawValues in TopLevelCodingKeys.
Apart from all that, you can use keyDecodingStrategy as .convertFromSnakeCase to handle underscore notation (snake case notation), i.e.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase //here.....
let model = try decoder.decode(CreatePostResponseModel.self, from: data)
print(model)
} catch {
print(error)
}
So, you don't need to explicitly handle all the snake-case keys. It'll be handled by the JSONDecoder on its own.
This can be one of the good solution for you wherever you want you can add multiple keys for one variable:
var transcodeId:String?
public init(from decoder: Decoder) throws {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
transcodeId = container.getValueFromAvailableKey(codingKeys: [CodingKeys._transcoder,CodingKeys._transcode])
} catch {
print("Error reading config file: \(error.localizedDescription)")
}
}
extension KeyedDecodingContainerProtocol{
func getValueFromAvailableKey(codingKeys:[CodingKey])-> String?{
for key in codingKeys{
for keyPath in self.allKeys{
if key.stringValue == keyPath.stringValue{
do{
return try self.decodeIfPresent(String.self, forKey: keyPath)
} catch {
return nil
}
}
}
}
return nil
}
}
Hope it helps.
The compiler-generated init(from:) is all-or-nothing. You can’t have it decode some keys and “manually” decode others.
One way to use the compiler-generated init(from:) is by giving your struct both of the possible encoded properties, and make transcodeId a computed property:
struct CreatePostResponseModel: Codable {
var transcodeId: String? {
get { _transcode ?? _transcoder }
set { _transcode = newValue; _transcoder = nil }
}
var _transcode: String? = nil
var _transcoder: String? = nil
var id: String = “”
// other properties
}

Swift 4 - Convert 1 or 0 from mysql to bool

I have this variable here, its an Any
item["completed"]
Its either 1 or 0 which is true or false from mysql database, how can I covert the 1 or 0 to true or false?
I have tried this:
(item["completed"] as! NSNumber).boolValue)
but I get this error
Could not cast value of type 'NSTaggedPointerString' (0x10f906560) to
'NSNumber' (0x10e455d40).
You can try below extension which i have implemented,
extension String {
var bool: Bool {
return self == "1" ? true : false
}
}
(item["completed"] as? String).bool
Hope this way may help you.
Just use
let isCompleted = "1" == (item["completed"] as? String)
If you are using Decodable for json parsing, you can use following ApiBool struct instead of Bool
struct ApiBool: Codable {
let value: Bool
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self.value = string == "1"
} else {
let intVal = try container.decode(Int.self)
self.value = intVal == 1
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.value ? 1 : 0)
}
}
Note As mentioned in comments, you got an String data type and it is expected to be an integer, this is caused by libmysql driver, you can connect to MySQL using mysqlnd driver instead, this will result in a JSON with proper numeric data types, following posts might help you configure the driver
https://stackoverflow.com/a/46690317/1244597
https://stackoverflow.com/a/45366230/1244597
#ahemadabbas-vagh shared a good and reusable approach. But if we talk about computer science, a string's Boolean equality should not be measured like that.
This approach is the same as his, but it asks Bool if the value is true or false, not to String. Bool should know is a value is true or false, not String.
var str = "1"
extension Bool {
static func from(stringValue str: String) -> Bool {
return str == "1"
}
}
if Bool.from(stringValue: str) {
print("TRUE")
} else {
print("FALSE")
}
But it is still not correct. In computer science, null/nil or/and an empty string should be false and any other strings should be true. You can add the string "0" to this set.
You can implement like this; better approaches still might exist.
The idea is; globally a string can only be converted to true if it is not "0", not "" (empty string), not null/nil or if it is "true".
var str = "1" // check for nil, empty string and zero; these should be false; any other string should be true.
extension Bool {
static func from(stringValue str: String) -> Bool {
if str == "true" {
return true
}
guard let intVal = Int(str) else {
return false
}
return Bool(truncating: intVal as NSNumber)
}
}
if Bool.from(stringValue: str) {
print("TRUE")
} else {
print("FALSE")
}
If you use Decodable protocol; I know you know how to handle but:
struct aStruct: Decodable {
let boolVal: Bool
enum CodingKeys: String, CodingKey {
case completed
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let boolString = try container.decode(String.self, forKey: .completed)
self.boolVal = Bool.from(stringValue: boolString)
}
}

Add default field to Swift Decodable object [duplicate]

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

How to handle JSON format inconsistencies with Swift 4 Codable?

I need to parse JSON of which one of the field value is either an array:
"list" :
[
{
"value" : 1
}
]
or an empty string, in case there's no data:
"list" : ""
Not nice, but I can't change the format.
I'm looking at converting my manual parsing, for which this was easy, to JSONDecoder and Codable struct's.
How can I handle this nasty inconsistency?
You need to try decoding it one way, and if that fails, decode it the other way. This means you can't use the compiler-generated decoding support. You have to do it by hand. If you want full error checking, do it like this:
import Foundation
struct ListItem: Decodable {
var value: Int
}
struct MyResponse: Decodable {
var list: [ListItem] = []
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
list = try container.decode([ListItem].self, forKey: .list)
} catch {
switch error {
// In Swift 4, the expected type is [Any].self, but I think it will be [ListItem].self in a newer Swift with conditional conformance support.
case DecodingError.typeMismatch(let expectedType, _) where expectedType == [Any].self || expectedType == [ListItem].self:
let dummyString = try container.decode(String.self, forKey: .list)
if dummyString != "" {
throw DecodingError.dataCorruptedError(forKey: .list, in: container, debugDescription: "Expected empty string but got \"\(dummyString)\"")
}
list = []
default: throw error
}
}
}
enum CodingKeys: String, CodingKey {
case list
}
}
If you want no error checking, you can shorten init(from:) to this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
list = (try? container.decode([ListItem].self, forKey: .list)) ?? []
}
Test 1:
let jsonString1 = """
{
"list" : [ { "value" : 1 } ]
}
"""
print(try! JSONDecoder().decode(MyResponse.self, from: jsonString1.data(using: .utf8)!))
Output 1:
MyResponse(list: [__lldb_expr_82.ListItem(value: 1)])
Test 2:
let jsonString2 = """
{
"list" : ""
}
"""
print(try! JSONDecoder().decode(MyResponse.self, from: jsonString2.data(using: .utf8)!))
Output 2:
MyResponse(list: [])

Resources