Merge Encodable in Swift - swift

I have the following Swift structs
struct Session: Encodable {
let sessionId: String
}
struct Person: Encodable {
let name: String
let age: Int
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
that I need to encode to a json object that has this format:
{
"name": "Jan",
"age": 36,
"sessionId": "xyz"
}
where all keys of the Session are merged into the keys of the Person
I thought about using a container struct with a custom Encodable implementation where I use a SingleValueEncodingContainer but it can obviously encode only one value
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(person)
// crash
try container.encode(session)
}
}
let person = Person(name: "Jan", age: 36)
let session = Session(sessionId: "xyz")
let requestModel = RequestModel(session: session, person: person)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(requestModel)
let json = String(data: data, encoding: .utf8)!
print(json)
I cannot change the json format as it's a fixed network API. I could have the sessionId as property of the Person but I'd like to avoid that as they are unrelated models.
Another way could be to have the RequestModel copy all the properties from the Session and Person as follows but it's not very nice as my real structs have much more properties.
struct RequestModel: Encodable {
let sessionId: String
let name: String
let age: Int
init(session: Session, person: Person) {
sessionId = session.sessionId
name = person.name
age = person.age
}
}

Call encode(to:) of each encodable objects, instead of singleValueContainer().
It makes possible to join multi encodable objects into one encodable object without defining extra CodingKeys.
struct RequestModel: Encodable {
let session: Session
let person: Person
public func encode(to encoder: Encoder) throws {
try session.encode(to: encoder)
try person.encode(to: encoder)
}
}

Use encoder.container(keyedBy: CodingKeys.self) instead of singleValueContainer() and add the key-value pairs separately, i.e.
struct RequestModel: Encodable
{
let session: Session
let person: Person
enum CodingKeys: String, CodingKey {
case sessionId, name, age
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(person.age, forKey: RequestModel.CodingKeys.age)
try container.encode(person.name, forKey: RequestModel.CodingKeys.name)
try container.encode(session.sessionId, forKey: RequestModel.CodingKeys.sessionId)
}
}
Output:
{
"age" : 36,
"name" : "Jan",
"sessionId" : "xyz"
}
Let me know in case you still face any issues.

I'd like to expand on #marty-suzuki's answer here, because there are a few nuances that may be missed if you're not careful. Here's my version of the code:
struct EncodableCombiner: Encodable {
let subelements: [Encodable]
func encode(to encoder: Encoder) throws {
for element in subelements {
try element.encode(to: encoder)
}
}
}
Simply instantiate with an array of codable objects and treat the resulting object as a codable object in its own right. Now, there are a couple of important notes to remember when using this method:
You can only have one type of root object in your JSON, which will either be a single value, an array, or a dictionary. So when you implement encode(to:) in your various codable objects, never create your container using encoder.singleValueContainer.
Every object you wish to combine must operate on the same kind of container, so if one of them uses unkeyedContainer(), so must they all. Similarly, if one uses container(keyedBy:) then the others must too.
If you're using keyed containers, then no two variables across all the combined objects can share the same key name! Otherwise, you're going to find that they overwrite each other since they're being parsed into the same dictionary.
An alternative that would alleviate these problems, but does not result in the same JSON structure, would be this:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(elementA)
try container.encode(elementB)
}
}
Now, this is slightly less convenient because we can't simply supply an array of objects that conform to Encodable; it needs to know exactly what they are to call container.encode(). The result is a JSON object with an array as its root object, and each subelement expressed as an element in that array. In fact, you could simplify it further like this:
struct EncodableCombiner: Encodable {
let elementA: MyEncodableA
let elementB: MyEncodableB
}
... which would of course result in a dictionary root object with the encoded form of MyEncodableA keyed as elementA, and MyEncodableB as elementB.
It all depends on what structure you want.

Related

How to encode nested objects into JSON (Swift)?

I have such an object that should be encoded to JSON (My Playground example)
struct Toy: Codable {
var name: String
enum GiftKeys: String, CodingKey {
case toy = "name"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: GiftKeys.self)
try container.encode(self, forKey: .toy)
}
}
struct Employee: Encodable {
var name: String
var id: Int
var favoriteToy: Toy
enum CodingKeys: CodingKey {
case name, id
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(id, forKey: .id)
try favoriteToy.encode(to: encoder)
}
}
do {
let toy = Toy(name: "Teddy Bear")
let employee = Employee(name: "John Appleseed", id: 7, favoriteToy: toy)
let encoder = JSONEncoder()
let nestedData: Data = try encoder.encode(employee)
let nestedString = String(data: nestedData, encoding: .utf8)!
print(nestedString)
}
What I am going to achieve is that each object knows how to encode itself. So, in my example when I through the employee object to the encoder, so each of the objects (employee and Toy) is responsible for its encoding.
But when I try to run the example in the Playground looks like it comes to the stuck in the line try favoriteToy.encode(to: encoder)
What is the problem here?
This line is the problem:
try container.encode(self, forKey: .toy)
The toy is trying to encode itself, so this will call encode(to:) again, causing infinite recursion. You probably meant:
try container.encode(name, forKey: .toy)
Remember that in the encode(to:) method, you are trying to answer the question of "how to encode a Toy?". If you answer with "just encode the toy itself!" That's not really answering the question, is it?
This will print:
{"name":"Teddy Bear","id":7}
Notice that the employee's name is overwritten by the toy's name, since you encoded the toy's name using the same encoder and same key, as the employee's name.
That's probably not what you intended. You probably wanted:
enum CodingKeys: CodingKey {
case name, id, toy
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(id, forKey: .id)
try container.encode(favoriteToy, forKey: .toy)
}
// {"name":"John Appleseed","id":7,"toy":{"name":"Teddy Bear"}}
Note that all this code can be generated by the compiler, if you remove all your CodingKeys and encode methods:
struct Toy: Codable {
var name: String
}
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy
}
What I am going to achieve is that each object knows how to encode itself.
Each object does know to encode itself if you leave it alone by omitting the CodingKeys and encode(to methods
struct Toy: Encodable {
var name: String
}
struct Employee: Encodable {
var name: String
var id: Int
var favoriteToy: Toy
}
You made two serious mistakes. If you implement encode(to you have to encode each struct member for the corresponding CodingKey rather than encoding self or calling encode(to: on a struct member.
So replace
try container.encode(self, forKey: .toy)
with
try container.encode(name, forKey: .toy)
and replace
try favoriteToy.encode(to: encoder)
with
try container.encode(favoriteToy, forKey: .favoriteToy)
and add also the missing CodingKey favoriteToy and the raw value type
private enum CodingKeys: String, CodingKey {
case name, id, favoriteToy
}
But in this case the best solution is not to implement encode(to

How to make a custom class Codable?

I'm using a thread safe dictionary found on Github
But how to make the ThreadSafeDictionary in that repo conform to the Codable?
It seems initialise a new lock in the encoder function would help? đŸ€”
You could provide a subclass of ThreadSafeDictionary which works with Codable Key and Value types. In my opinion it's best to pretend that we are dealing with a simple dictionary, so lets use a singleValueContainer.
class CodableThreadSafeDictionary<Key: Codable & Hashable, Value: Codable>: ThreadSafeDictionary<Key, Value>, Codable {
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dictionary = try container.decode(DictionaryType.self)
protectedCache = CZMutexLock(dictionary)
}
public required init(dictionaryLiteral elements: (Key, Value)...) {
var dictionary = DictionaryType()
for (key, value) in elements {
dictionary[key] = value
}
protectedCache = CZMutexLock(dictionary)
super.init()
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
var dictionary = DictionaryType()
protectedCache.readLock {
dictionary = $0
}
try container.encode(dictionary)
}
}
But because of the fact that the protectedCache is a fileprivate property, you will need to put this implementation in the same file
Note!
You could consider limiting Key to just a String for a better compatibility with JSON format:
CodableThreadSafeDictionary<Key: String, Value: Codable>

Swift custom decodable initializer without CodingKeys

Let's say I have the following decodable struct as an example illustrating what I'm trying to do:
struct Object: Decodable {
var id: String
var name: String
}
and this JSON:
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
Notice that the name property can be null sometimes. This would mostly work fine like it is since the json keys match the struct property names, so I don't need a CodingKey enum, but the name property can be null sometimes. However, instead of making name optional, I want to substitute a default string, so I need a custom initializer:
struct Object: Decodable {
var id: String
var name: String
init(from decoder: Decoder) throws {
...
self.name = <value from decoder> ?? "default name"
...
}
}
But this requires a CodingKey object. I'm using the default keys. Do I need a CodingKey enum as well now? Even though all my keys match up? Or is there a way to have a custom Decodable initializer using just the keys as they are?
Is there maybe some sort of default container I can use?
let container = try decoder.container(keyedBy: <defaultContainer???>)
I tried using both of these variants, but neither worked:
let container = try decoder.singleValueContainer()
let container = try decoder.unkeyedContainer()
How can I have a custom Decodable intializer but also use the default keys?
The issue is that CodingKeys is only automatically generated for you if didn’t fully manually conform to the relevant protocols. (This is very familiar for Objective-C developers, where a property’s backing ivar would not be automatically synthesized if you manually implemented all the relevant accessor methods.)
So, in the following scenarios, the CodingKeys is not created automatically for you:
You adopt only Decodable and implemented your own init(from:);
You adopt only Encodable and implemented your own encode(to:); or
You adopt both Encodable and Decodable (or just to Codable) and implemented your own init(from:) and encode(to:).
So your case falls within the first scenario, above.
It has been suggested that you can get around your conundrum by adopting Codable, even though you’ve only implemented init(from:) and presumably don’t plan on ever using the Encodable behavior. In effect, that is relying upon a side effect of a protocol you don’t plan on really using.
It doesn’t really matter too much, and adopting Codable works, but it might be considered to be “more correct” to go ahead and define your CodingKeys, rather than relying on the unimplemented Encodable side-effect:
struct Object: Decodable {
var id: String
var name: String
enum CodingKeys: String, CodingKey {
case id, name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = (try? container.decode(String.self, forKey: .name)) ?? "Default Value"
}
}
The auto-generation for CodingKeys is really weird. The scope and availability of it changes based on what members you have.
Say you just have a Decodable. These compile:
struct Decodable: Swift.Decodable {
static var codingKeysType: CodingKeys.Type { CodingKeys.self }
}
struct Decodable: Swift.Decodable {
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}

and you can put them together, if you add private.
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}

But make that func an initializer, and again, no compilation.
struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
You can change it to be fully Codable, not just Decodable

struct Decodable: Codable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}
But then you can't use CodingKeys at type scope, so the property won't compile.
Considering you probably don't need such a property, just use Codable, file a bug with Apple referencing this answer, and hopefully we can all switch to Decodable when they fix it. đŸ˜ș
You can emulate the behavior using property wrappers but it's not the perfect solution and it's a bit hacky. The problem has been discussed on Swift forums multiple times already.
With a property wrapper:
struct Object: Decodable {
var id: String
#DecodableDefault var name: String
}
Code for the property wrapper:
public protocol DecodableDefaultValue: Decodable {
static var defaultDecodableValue: Self { get }
}
#propertyWrapper
public struct DecodableDefault<T: Decodable>: Decodable {
public var wrappedValue: T
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(T.self)
}
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
}
extension DecodableDefault: Encodable where T: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
extension DecodableDefault: Equatable where T: Equatable { }
extension DecodableDefault: Hashable where T: Hashable { }
public extension KeyedDecodingContainer {
func decode<T: DecodableDefaultValue>(_: DecodableDefault<T>.Type, forKey key: Key) throws -> DecodableDefault<T> {
guard let value = try decodeIfPresent(DecodableDefault<T>.self, forKey: key) else {
return DecodableDefault(wrappedValue: T.defaultDecodableValue)
}
return value
}
}
extension Array: DecodableDefaultValue where Element: Decodable {
public static var defaultDecodableValue: [Element] {
return []
}
}
extension Dictionary: DecodableDefaultValue where Key: Decodable, Value: Decodable {
public static var defaultDecodableValue: [Key: Value] {
return [:]
}
}
extension String: DecodableDefaultValue {
public static let defaultDecodableValue: String = ""
}
extension Int: DecodableDefaultValue {
public static let defaultDecodableValue: Int = 0
}
To list a few problems:
you cannot select the default value (can be done differently but it's more complicated)
if you want to use let, you need a separate immutable wrapper.
As a comment made on your question says the compiler generates a CodingKeys object for you. You can implement a custom enum when the keys mismatch names on your enum or class respecting the JSON data you're receiving from the source.
You can implement your object this way:
struct TestObject: Codable {
var id: String
var name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let nameOrNil = try? container.decode(String.self, forKey: .name)
self.id = id
self.name = nameOrNil ?? "Default value"
}
}
The container's decode(_:forKey:) method can throw and error but if you make the implementation to discharge the error and instead returning an optional value with try? you can apply a nil coalescing operator to assign your default value whenever the name is not included on the JSON.
Proof here:
let json = """
[
{
"id": "a",
"name": "test"
},
{
"id": "b",
"name": null
}
]
""".data(using: .utf8)!
let decodedArray = try JSONDecoder().decode([TestObject].self, from: json)
print(decodedArray)
References:
https://medium.com/swiftly-swift/swift-4-decodable-beyond-the-basics-990cc48b7375
?? operator in Swift
https://developer.apple.com/documentation/swift/decodable

Swift reusable model class/struct

I have a service whose response vary based on the orderNumer(input parameter which I pass to service). all most all object are same but only one object(ex meta object which is dictionary)vary based on order number. But I would like to reuse the same model class everywhere but due to different meta object can't be able to create that meta object in model class.I can achieve it by creating individual model class but not a right solution.
struct BookingInfo: Codable {
let created_time: Int
// Some other 20 key value pairs
let oms_meta: Meta /* oms_meta": {
"package": {
"description": "gzhdjjdjd"
}*/
}
// Meta for Order1
struct Meta: Codable {
let package: Description
enum CodingKeys : String, CodingKey {
case package = "package"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
package = try values.decode(Description.self, forKey: .package)
}
}
// Meta for Order 2
struct Meta: Codable {
let customer_payment_details: Int
let items: Int // this can be anything dictinary or some time array of dictionary
enum CodingKeys : String, CodingKey {
case package = "customer_payment_details"
case items = "items"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
customer_payment_details = try values.decode(Int.self, forKey: .package)
items = try values.decode(Int.self, forKey: .package)
}
}
Only meta parameters are varying from service to service.
Thanks in advance
If you want to only use 1 model, you should provide a variable for all the possible keys in the dictionary and set them as optional.
Let's say all orders have an orderNumber, but only some have an orderImage you could do it like this:
struct Order: Codable {
var orderNumber: Int
var orderImage: Data?
var orderName: String?
var orderDescription: String?
}
This way, the returned data only has to contain an orderNumber to be able to decode it.
Edit: You'd have to make a seperate model for each type of 'meta data' as they seem to be very different from eachother. Note that there is nothing wrong with creating a lot of model classes as long as they represent a specific data object.
[
{
"created_time": 1,
"oms_meta": {
"name": "1"
}
},
{
"created_time": 1,
"oms_meta": [
1
]
}
]
Suppose we have a response something like this, where you can have multiple oms_meta objects. So what we can do here is to specify an enum which will contain both array as well as dictionary, it doesn't matter if the service sends you array or dictionary if you need to specify other data types mentioned it in that enum. So it will be like,
struct BookingInfoElement: Codable {
let createdTime: Int?
let omsMeta: OmsMetaUnion?
enum CodingKeys: String, CodingKey {
case createdTime = "created_time"
case omsMeta = "oms_meta"
}
}
enum OmsMetaUnion: Codable {
case integerArray([Int])
case omsMetaClass(OmsMetaClass)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([Int].self) {
self = .integerArray(x)
return
}
if let x = try? container.decode(OmsMetaClass.self) {
self = .omsMetaClass(x)
return
}
throw DecodingError.typeMismatch(OmsMetaUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for OmsMetaUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integerArray(let x):
try container.encode(x)
case .omsMetaClass(let x):
try container.encode(x)
}
}
}
struct OmsMetaClass: Codable {
let name: String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
}
typealias BookingInfo = [BookingInfoElement]
So, if you specifically want to have multiple types for a single variable you can create another enum with that multiple types and assign accordingly. You can access that variable using switch like, and check on array and dictionary types.
After doing some analyses I found the solution for my requirement. As I wanted to reuse the Model class and OMS_Meta dictionary different from product to product, I'm adding omsMeta to JSONDecoder().userInfo property
struct BookingInfo: Codable {
let created_time: Int
let oms_meta: [String: Any]
}
public static let metaUserInfo = CodingUserInfoKey(rawValue: "oms_meta")!
decoder.userInfo[BookingInfo.metaUserInfo] = jsonDecode
By doing this I can re use my model class.

Swift Codable: Is it possible to encode a structure one level up from within its own "encode(to:)" function?

I'm using a protocol to encode the conforming structures:
protocol RequestParameters: Encodable {
}
extension RequestParameters {
func dataEncoding() -> Data? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return data
}
}
This works perfectly fine for encoding these kind of structures:
struct StoreRequest: RequestParameters {
var storeCode : String
var storeNumber : String
}
However, sometimes my requests require some "shared" parameters:
struct SpecialStoreRequest: RequestParameters {
var storeCode : String
var storeNumber : String
// Shared Parameters that appear in 70% of my requests
var sharedParam1 : String?
var sharedParam2 : String?
var sharedParam3 : String?
var sharedParam4 : String?
var sharedParam5 : String?
}
I could simply write these shared parameters on each of my request structures that need them, but I was wondering if it is possible to group them in another structure and somehow modify the encoding to encode them on the top level instead?
I'm thinking of something similar to this:
struct SharedParameters {
// Shared Parameters that appear in 70% of my requests
var sharedParam1: String?
var sharedParam2: String?
var sharedParam3: String?
var sharedParam4: String?
var sharedParam5: String?
enum CodingKeys: String, CodingKey {
case sharedParam1
case sharedParam2
case sharedParam3
case sharedParam4
case sharedParam5
}
}
struct SpecialStoreRequest: RequestParameters {
var storeCode : String
var storeNumber : String
var sharedParams : SharedParameters?
}
The problem with this last structure is that the resulting encoding is NOT the same as the first one because my shared parameters will be encoded INSIDE the "sharedParams" key:
{
"storeCode" : "ABC",
"storeNumber" : "123456",
"sharedParams" : {"sharedParam1" : "A","sharedParam2" : "B", ...}
}
But what I need is for them be encoded along my other existing parameters (storeCode & storeNumber in this case).
{
"storeCode" : "ABC",
"storeNumber" : "123456",
"sharedParam1" : "A",
"sharedParam2" : "B",
...
}
EDIT:
To make the question clearer, assuming it is possible, what should go here to make this structure be encoded by key-value directly on its parent?
extension SharedParameters: Encodable {
func encode(to encoder: Encoder) throws {
// What goes here? (Is it even possible?)
}
}
was wondering if it is possible to group them in another structure and somehow modify the encoding to encode them on the top level instead?
You can't change the current Encoder and how it behaves but,
You can achieve that by customizing the Encode functions,
make two containers and use the shared parameters CodingKeys to encode
parameters inside your sharedParameters variable.
Observe the code Below.
struct Others: Codable {
var sharedParam1: String
var sharedParam2: String
enum CodingKeys: String, CodingKey {
case sharedParam1
case sharedParam2
}
}
struct MyCustomReq: Codable {
var p1: String
var p2: String
var shared: Others
enum CodingKeys: String, CodingKey {
case p1
case p2
case shared
}
}
extension MyCustomReq {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(p1, forKey: .p1)
try container.encode(p2, forKey: .p2)
//Others = sharedParams container. with its CodingKeys
var container2 = encoder.container(keyedBy: Others.CodingKeys.self)
try container2.encode(shared.sharedParam1, forKey: .sharedParam1 )
try container2.encode(shared.sharedParam1, forKey: .sharedParam2)
}
}
Usage Test
var oth = Others(sharedParam1: "Shared1", sharedParam2: "Shared2")
var object = MyCustomReq.init(p1: "P1", p2: "P2", shared: oth)
let encoder = JSONEncoder()
let data = try encoder.encode(object)
print(String(data: data, encoding: .utf8)!)
OUTPUT
{
"p2":"P2",
"sharedParam1":"Shared1",
"p1":"P1",
"sharedParam2":"Shared1"
}
Now lets take it to the next step,
Create a class and custom the Encoder of the shared there and just call its function.
Observe the final result.
final class MyParamsEncoded: Codable {
var sharedParams: Others
init (sharedParam: Others) {
self.sharedParams = sharedParam
}
func encode(to encoder: Encoder) throws {
var container2 = encoder.container(keyedBy: Others.CodingKeys.self)
try container2.encode(sharedParams.sharedParam1, forKey: .sharedParam1 )
try container2.encode(sharedParams.sharedParam1, forKey: .sharedParam2)
}
}
Now after Adding this class you can just use it like this,
And it will give you the same result.
extension MyCustomReq {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(p1, forKey: .p1)
try container.encode(p2, forKey: .p2)
//Using the class wrapping, final Result of using
var cont = try MyParamsEncoded(sharedParam: shared).encode(to: encoder)
}
}