Reputation: 35
I want to have classes objects JSONSerialization
. So my input is [String: Any]
and from a documentation I know it may be either NSNull
, NSString
or NSNumber
. So I've made a protocol:
protocol PlainValue { }
and all of those above conform to this protocol:
extension NSString: PlainValue { }
extension NSNull: PlainValue { }
extension NSNumber: PlainValue { }
Then I want to create a class storage that hold key-value like:
class KeyValue<T: PlainValue> {
let key: NSString
let value: T
init(key: NSString, value: T) {
self.key = key
self.value = value
}
}
And want to use it like this:
func parse(json: [String: Any]) {
...
if let value = json[key] as? PlainValue { // this should be `Any` but I want to check here if thats an PlainValue or embedded object
let obj = KeyValue<PlainValue>(key: key, value: value) // this currently not working
...
}
...
}
But my issue is how to declare this object creation on protocol level. If I do this like:
protocol PlainValue {
func convert(key: NSString) -> KeyValue<PlainValue>
}
I'm getting error:
Value of protocol type 'PlainValue' cannot conform to 'PlainValue'; only struct/enum/class types can conform to protocols
Make sense, since I'm already in protocol declaration. So I have a feeling that maybe this should be declared on KeyValue<T>
level? But I'm not sure if I'm on a right path for this, since I'm getting compilers error on every approach I'm trying to make. Can anyone point me into right direction how to make it working?
Approach that I feel like it's closest to working is:
extension NSNumber: PlainValue {
func convert(key: NSString) -> KeyValue<NSNumber> {
return KeyValue<NSNumber>(key: key, value: self)
}
}
And similar for above on NSString
and NSNull
, but not sure how to declare this on protocol level to make this callable from my parsing function. Since
protocol PlainValue: JSONValue {
func convert(key: NSString) -> KeyValue<Self> // this claims that implementation does not match this declaration
func convert<T: PlainValue>(key: NSString) -> KeyValue<T> // same as above
associatedtype Object: PlainValue
func convert(key: NSString) -> KeyValue<Object> // this is working! But... then I cannot check if value is PlainValue in my parsing function, because of `Protocol 'PlainValue' can only be used as a generic constraint because it has Self or associated type requirements`
}
Upvotes: 0
Views: 109
Reputation: 31
Not sure if I understand correctly what you would like to achieve, but based on what you wrote, you don't necessarily need generics here. The code below would resolve your issue, if there are no other factors that make generics necessary in your situation. Also, this way you won't need to declare the object creation on the protocol level.
class KeyValue {
let key: NSString
let value: PlainValue
init(key: NSString, value: PlainValue) {
self.key = key
self.value = value
}
}
Your original example did not work because you needed a concrete type there that conforms to PlainValue. In swift, only concrete types such as struct/enum/class can conform to protocols. For example let obj = KeyValue<NSString>(key: key, value: value)
would work there, while using PlainValue would not.
Upvotes: 1
Reputation: 285130
A protocol cannot conform to another protocol, the generic type T
in KeyValue<T>
must be a concrete type.
An alternative is a protocol extension with an associated type
extension NSString: PlainValue { }
extension NSNull: PlainValue { }
extension NSNumber: PlainValue { }
protocol PlainValue {
associatedtype ValueType : PlainValue = Self
func convert(key: NSString) -> KeyValue<ValueType>
}
extension PlainValue where ValueType == Self {
func convert(key: NSString) -> KeyValue<ValueType> {
return KeyValue<ValueType>(key: key, value: self)
}
}
class KeyValue<T: PlainValue> {
let key: NSString
let value: T
init(key: NSString, value: T) {
self.key = key
self.value = value
}
}
let stringResult = "Foo".convert(key:"bar")
let numberResult = NSNumber(value:12).convert(key:"baz")
let nullResult = NSNull().convert(key:"buz")
Upvotes: 1