У меня есть Dictionary<ObjcType, SwiftType>
, который мне нужно сериализовать и десериализовать из JSON. SwiftType
соответствует Codable
. ObjcType
— это класс, написанный на Objective-C (используется одной из библиотек, от которых я сильно завишу, поэтому не могу изменить его на Swift). Все свойства ObjcType
являются либо NSString
, либо BOOL
.
Я провел поиск и добавил эти функции в ObjcType
, чтобы придать ему некоторое подобие сериализации:
-(NSString*)GetJSON{
NSError *writeError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted error:&writeError];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return jsonString;
}
- (instancetype)initWithObject:(NSString *)jsonString {
self = [super init];
if (self != nil)
{
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *object = [NSJSONSerialization
JSONObjectWithData:jsonData
options:0
error:&error];
for (NSString *dictionaryKey in object) {
self.field1 = [[object valueForKey:dictionaryKey] objectForKey:@"field1"];
self.field2 = [[object valueForKey:dictionaryKey] objectForKey:@"field2"];
self.field3 = [[object valueForKey:dictionaryKey] objectForKey:@"field3"];
}
}
return self;
}
Итак, теперь мой ObjcType
может создать свой собственный текст и поместить его в этот JSON.
Но я не могу понять, как сделать обертку, которая будет кодировать\декодировать этот словарь. Любая помощь?
P.S. еще лучше, если он сможет работать с модулем OrderedDictionary
из OrderedCollections
вместо обычного Dictionary
.
Порядок на самом деле не важен для сохранения, он важен только для других операций. Именно поэтому я и спросил про нормальный Словарь.
Этот ответ должен частично ответить на ваш вопрос.
В чистом Swift вы можете кодировать/декодировать словарь, если его тип Key
соответствует CodingKeyRepresentable
.
В качестве примера давайте рассмотрим следующие два типа Swift:
struct ObjcType: Codable, Hashable {
let foo, bar: String, baz: Bool
}
struct SwiftType: Codable {
let rom, ram: String, rem: Int
}
Вы можете соответствовать ObjcType
CodingKeyRepresentable
следующим образом:
extension ObjcType: CodingKeyRepresentable {
struct StringCodingKey: CodingKey {
var intValue: Int? { nil }
let stringValue: String
init?(intValue: Int) { return nil }
init(stringValue value: String) { stringValue = value }
}
var codingKey: any CodingKey {
let encoder = JSONEncoder()
let data = try! encoder.encode(self)
let string = String(data: data, encoding: .utf8)!
return StringCodingKey(stringValue: string)
}
init?(codingKey: some CodingKey) {
let decoder = JSONDecoder()
guard
let data = codingKey.stringValue.data(using: .utf8),
let value = try? decoder.decode(Self.self, from: data)
else { return nil }
self = value
}
}
Здесь codingKey
возвращает CodingKey
, stringValue
которого является строковым представлением JSON экземпляра ObjcType
, а init(codingKey:)
инициализирует экземпляр путем декодирования строкового значения ключа кодирования.
Со временем они смогут использовать определенные вами функции GetJSON
и initWithObject
.
Имея это соответствие, вы можете кодировать:
let encodingValue: [ObjcType: SwiftType] = [
.init(foo: "one", bar: "two", baz: true): .init(rom: "uno", ram: "due", rem: 3),
.init(foo: "one", bar: "two", baz: false): .init(rom: "uno", ram: "due", rem: 0),
.init(foo: "un", bar: "dos", baz: true): .init(rom: "one", ram: "two", rem: 6),
]
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try print(String(data: encoder.encode(encodingValue), encoding: .utf8)!)
// {
// "{\"foo\":\"un\",\"bar\":\"dos\",\"baz\":true}" : {
// "rom" : "one",
// "ram" : "two",
// "rem" : 6
// },
// "{\"foo\":\"one\",\"bar\":\"two\",\"baz\":true}" : {
// "rom" : "uno",
// "ram" : "due",
// "rem" : 3
// },
// "{\"foo\":\"one\",\"bar\":\"two\",\"baz\":false}" : {
// "rom" : "uno",
// "ram" : "due",
// "rem" : 0
// }
// }
и декодируем:
let encodedValue = #"""
{
"{\"foo\":\"uno\",\"bar\":\"due\",\"baz\":true}" : {
"rom": "one",
"ram": "two",
"rem": 3
},
}
"""#
try dump(JSONDecoder().decode(
[ObjcType: SwiftType].self, from: encodedValue.data(using: .utf8)!
))
// ▿ 1 key/value pair
// ▿ (2 elements)
// ▿ key: ObjcType
// - foo: "uno"
// - bar: "due"
// - baz: true
// ▿ value: SwiftType
// - rom: "one"
// - ram: "two"
// - rem: 3
Проблема в том, что init
, требуемый CodingKeyRepresentable
, должен быть required init
, поскольку ObjcType
— это класс. Однако required init
нельзя объявлять в расширении.
Вы можете написать обертку следующим образом:
@propertyWrapper
struct Wrapper: Codable {
struct StringCodingKey: CodingKey {
var intValue: Int? { nil }
let stringValue: String
init?(intValue: Int) { return nil }
init(stringValue value: String) { stringValue = value }
}
var wrappedValue: [ObjcType: SwiftType]
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
for (key, value) in wrappedValue {
// assuming there is property called 'jsonString'
let keyString = key.jsonString
try container.encode(value, forKey: StringCodingKey(stringValue: keyString))
}
}
init(wrappedValue: [ObjcType : SwiftType]) {
self.wrappedValue = wrappedValue
}
init(from decoder: any Decoder) throws {
var wrapped = [ObjcType: SwiftType]()
let container = try decoder.container(keyedBy: StringCodingKey.self)
for key in container.allKeys {
// assuming such an initialiser exists
let objcKey = ObjcType(json: key.stringValue)
wrapped[objcKey] = try container.decode(SwiftType.self, forKey: key)
}
self.wrappedValue = wrapped
}
}
Использование:
// decoding
JSONDecoder().decode(Wrapper.self, from: someJson).wrappedValue
// encoding
JSONEncoder().encode(Wrapper(wrapped: yourActualDictionary))
Вы также можете использовать его как обертку свойств в других типах Codable
:
struct SomeOtherCodableType: Codable {
@Wrapper var dict: [ObjcType: SwiftType]
}
JSON поддерживает только строки для ключей и не поддерживает порядок, поэтому использование
OrderedDictionary
ничем не поможет. Люди, вероятно, могли бы ответить на этот вопрос, но вы можете помочь им, предоставив им короткий/простой фрагмент, который они смогут легко скопировать/вставить и отредактировать, показывая тип словаря, который вы хотите сериализовать.