Use Multiple Codingkeys for a Single Property

Use multiple CodingKeys for a single property

You can't do what you're asking to do. From the question and your later comments, it appears you've got some very bad JSON. Decodable is not made for that sort of thing. Use JSONSerialization and clean up the mess afterward.

How to use 2 coding keys for same struct in swift using Codable Protocol

A neglected approach is a custom keyDecodingStrategy, however this requires a dummy CodingKey struct.

struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?

init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { self.stringValue = String(intValue) }
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({
let currentKey = $0.last!
if currentKey.stringValue == "first_Name" {
return AnyKey(stringValue: "firstName")!
} else {
return currentKey
}
})

Decodable JSONDecoder handle different coding keys for the same value

You have to write a custom initializer to handle the cases, for example

struct Thing : Decodable {
let scanCode, name, scanId : String

private enum CodingKeys: String, CodingKey { case scanCode = "ScanCode", name = "Name", ScanID, Id }

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scanCode = try container.decode(String.self, forKey: .scanCode)
name = try container.decode(String.self, forKey: .name)
if let id = try container.decodeIfPresent(String.self, forKey: .Id) {
scanId = id
} else {
scanId = try container.decode(String.self, forKey: .ScanID)
}
}
}

First try to decode one key, if it fails decode the other.

For convenience I skipped the attributes key

Swift structures: handling multiple types for a single property

I ran into the same issue when trying to decode/encode the "edited" field on a Reddit Listing JSON response. I created a struct that represents the dynamic type that could exist for the given key. The key can have either a boolean or an integer.

{ "edited": false }
{ "edited": 123456 }

If you only need to be able to decode, just implement init(from:). If you need to go both ways, you will need to implement encode(to:) function.

struct Edited: Codable {
let isEdited: Bool
let editedTime: Int

// Where we determine what type the value is
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

// Check for a boolean
do {
isEdited = try container.decode(Bool.self)
editedTime = 0
} catch {
// Check for an integer
editedTime = try container.decode(Int.self)
isEdited = true
}
}

// We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try isEdited ? container.encode(editedTime) : container.encode(false)
}
}

Inside my Codable class, I then use my struct.

struct Listing: Codable {
let edited: Edited
}

Edit: A more specific solution for your scenario

I recommend using the CodingKey protocol and an enum to store all the properties when decoding. When you create something that conforms to Codable the compiler will create a private enum CodingKeys for you. This lets you decide on what to do based on the JSON Object property key.

Just for example, this is the JSON I am decoding:

{"type": "1.234"}
{"type": 1.234}

If you want to cast from a String to a Double because you only want the double value, just decode the string and then create a double from it. (This is what Itai Ferber is doing, you would then have to decode all properties as well using try decoder.decode(type:forKey:))

struct JSONObjectCasted: Codable {
let type: Double?

init(from decoder: Decoder) throws {
// Decode all fields and store them
let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum.

// First check for a Double
do {
type = try container.decode(Double.self, forKey: .type)

} catch {
// The check for a String and then cast it, this will throw if decoding fails
if let typeValue = Double(try container.decode(String.self, forKey: .type)) {
type = typeValue
} else {
// You may want to throw here if you don't want to default the value(in the case that it you can't have an optional).
type = nil
}
}

// Perform other decoding for other properties.
}
}

If you need to store the type along with the value, you can use an enum that conforms to Codable instead of the struct. You could then just use a switch statement with the "type" property of JSONObjectCustomEnum and perform actions based upon the case.

struct JSONObjectCustomEnum: Codable {
let type: DynamicJSONProperty
}

// Where I can represent all the types that the JSON property can be.
enum DynamicJSONProperty: Codable {
case double(Double)
case string(String)

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

// Decode the double
do {
let doubleVal = try container.decode(Double.self)
self = .double(doubleVal)
} catch DecodingError.typeMismatch {
// Decode the string
let stringVal = try container.decode(String.self)
self = .string(stringVal)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}

When to use CodingKeys in Decodable(Swift)

First of all there is a make-or-break rule for using CodingKeys:

  • You can omit CodingKeys completely if the JSON – or whatever Codable conforming format – keys match exactly the corresponding properties (like in your example) or the conversion is covered by an appropriate keyDecodingStrategy.

  • Otherwise you have to specify all CodingKeys you need to be decoded (see also reason #3 below).


There are three major reasons to use CodingKeys:

  1. A Swift variable/property name must not start with a number. If a key does start with a number you have to specify a compatible CodingKey to be able to decode the key at all.
  2. You want to use a different property name.
  3. You want to exclude keys from being decoded for example an id property which is not in the JSON and is initialized with an UUID constant.

And CodingKeys are mandatory if you implement init(from decoder to decode a keyed container.

Swift custom decodable initializer without CodingKeys

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. /p>


Related Topics



Leave a reply



Submit