How to simplify almost equal enum extensions in Swift

I have extensions for about 20 listings that look like this:

extension CurrencyValue : JSONDecodable { static func create(rawValue: String) -> CurrencyValue { if let value = CurrencyValue(rawValue: rawValue) { return value } return .unknown } static func decode(j: JSONValue) -> CurrencyValue? { return CurrencyValue.create <^> j.value() } } extension StatusValue : JSONDecodable { static func create(rawValue: String) -> StatusValue { if let value = StatusValue(rawValue: rawValue) { return value } return .unknown } static func decode(j: JSONValue) -> StatusValue? { return StatusValue.create <^> j.value() } } 

They are almost identical except for the name of the enumeration type, and I have 20 of them - this is obviously very stupid. Does anyone know how to reduce them to one, possibly using generics? At the moment, I have no idea.

UPDATE

The listings are simple:

 enum CurrencyValue : String { case EUR = "EUR" case unknown = "unknown" } enum StatusValue : String { case ok = "ok" case pending = "pending" case error = "error" case unknown = "unknown" } 

And let's assume the following:

  • Every ENUM has an unknown case.
  • I need to exchange an enum type in an extension with something in common.

There must be some trick not to implement the same extension several times, but just to change the type.

UPDATE

As Gregory Higley said below, I am using JSON lib Argo . You can read about them.

+6
source share
3 answers

The essence of your question is that you want to avoid writing template code for all of these enumerations when implementing Argo JSONDecodable . It looks like you also added the create method, which is not part of the signature of the JSONDecodable type:

 public protocol JSONDecodable { typealias DecodedType = Self class func decode(JSONValue) -> DecodedType? } 

Unfortunately this is not possible. Fast protocols are not mixes. With the exception of statements, they cannot contain any code. (I really hope this will be fixed in a future Swift update. The excellent default implementations for the protocols will be awesome.)

You can, of course, simplify your implementation in several ways:

  • As suggested by Tony DiPaskale, get rid of .unknown and use the optional ones. (Also, you would have to call it .unknown . The convention for Swift enumeration values ​​is to capitalize them. Proof? Look at every enum that Apple made. I can't find a single example where they start with a lowercase letter .)
  • Using options, your create now just a functional alias for init? and can be implemented very simply.
  • As Tony suggested, create a global generic function to handle decode . What he did not offer, although he suggested that it was implied, was to use this to implement JSONDecodable.decode .
  • As a meta suggestion, use the Xcode Code Snippets function to create a snippet to do this. It must be very fast.

At the request of the appellant, there is a quick implementation from the playground. I have never used Argo. In fact, I never heard of this until I saw this question. I answered this question by simply applying what I know about Swift to the consideration of the source of Argo and reasoning. This code is copied directly from the playground. He does not use Argo, but uses a reasonable facsimile of the corresponding parts. Ultimately, this question is not about Argo. This is a system like Swift, and everything in the code below correctly answers the question and proves that it is operational:

 enum JSONValue { case JSONString(String) } protocol JSONDecodable { typealias DecodedType = Self class func decode(JSONValue) -> DecodedType? } protocol RawStringInitializable { init?(rawValue: String) } enum StatusValue: String, RawStringInitializable, JSONDecodable { case Ok = "ok" case Pending = "pending" case Error = "error" static func decode(j: JSONValue) -> StatusValue? { return decodeJSON(j) } } func decodeJSON<E: RawStringInitializable>(j: JSONValue) -> E? { // You can replace this with some fancy Argo operators, // but the effect is the same. switch j { case .JSONString(let string): return E(rawValue: string) default: return nil } } let j = JSONValue.JSONString("ok") let statusValue = StatusValue.decode(j) 

This is not pseudo code. It is copied directly from the Xcode workspace.

If you create the RawStringInitializable protocol, and all its enumerations implement it, you will be gold. Since your enumerations are associated with String raw values, they implicitly implement this interface anyway. You just need to make an expression. The global function decodeJSON uses this protocol to process all your enumerations polymorphically.

+3
source

If you can also provide an example of an enumeration, it may be easier to see what can be improved.

Just by looking at it, I would say that you might want to remove .unknown from all enumerations and just have an optional type representing the .unknown value with .None . If you did this, you could write:

 extension StatusValue: JSONDecodable { static func decode(j: JSONValue) -> StatusValue? { return j.value() >>- { StatusValue(rawValue: $0) } } } 

Now there is no need to create all the creation functions. This will reduce a lot of duplication.

Edit:

Perhaps you can use Generics. If you create the DecodableStringEnum protocol as follows:

 protocol DecodableStringEnum { init?(rawValue: String) } 

Then do all your listings. You no longer need to write code because this init comes with raw enumerations.

 enum CreatureType: String, DecodableStringEnum { case Fish = "fish" case Cat = "cat" } 

Now write a global function to handle all of these cases:

 func decodeStringEnum<A: DecodableStringEnum>(key: String, j: JSONValue) -> A? { return j[key]?.value() >>- { A(rawValue: $0) } } 

Finally, in Argo, you can make your decode function of your creature look like this:

 static func decode(j: JSONValue) -> Creature? { return Creature.create <^> j <| "name" <*> decodeStringEnum("type", j) <*> j <| "description" } 
+1
source

For anyone else, the most recent answer would be: https://github.com/thoughtbot/Argo/blob/td-decode-enums/Documentation/Decode-Enums.md

Essentially for transfers

 enum CurrencyValue : String { case EUR = "EUR" case unknown = "unknown" } enum StatusValue : String { case ok = "ok" case pending = "pending" case error = "error" case unknown = "unknown" } 

You only need to do

 extension CurrencyValue: Decodable {} extension StatusValue: Decodable {} 

Also, as has been shown many times, if you just get rid of the .Unknown field and use options instead, you can use the built-in enumeration support as the RawRepresentable type.

See also: https://github.com/thoughtbot/Argo/blob/master/Argo/Extensions/RawRepresentable.swift for an implementation that makes this possible.

+1
source

Source: https://habr.com/ru/post/980730/


All Articles