Как лучше всего использовать структуры Codable с вызовами PATCH?

Как лучше всего использовать структуры Codable с вызовами PATCH?

Думаю, мне нужно знать, какие свойства моей структуры были обновлены, а затем передавать только эти значения в качестве частичных обновлений для вызова патча.

Нужно ли мне использовать наблюдателей DidSet для всех свойств, чтобы я мог отметить, какой из них изменился, или есть лучший способ?

Спасибо

ОБНОВЛЕНИЕ: пожалуйста, проверьте комментарии к вопросу

«Нужно ли мне использовать наблюдателей DidSet для всех свойств, чтобы я мог отметить, какое из них изменилось?» Это звучит правильно, если вы просто хотите знать, какие свойства изменились. Это будет не так утомительно, если вы напишете несколько макросов, которые сделают это за вас. Если я правильно понимаю, вы хотите, чтобы реализация Encodable кодировала только те свойства, которые изменились с... когда? Я предполагаю, что должно быть что-то вроде метода reset, который сбрасывает все флаги?

Sweeper 13.07.2024 10:37

@Sweeper Да, все правильно. Я спрашиваю, есть ли лучший подход к быстрой обработке вызовов PATCH? Codable обычно используются для передачи всех данных для вызовов POST. Что делать, если вам приходится иметь дело с частичными обновлениями?

aneuryzm 13.07.2024 11:25
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я придумал один из способов — использовать подобную обертку свойства, чтобы связать флаг didSet с интересующими вас свойствами:

@propertyWrapper
public struct DidSetFlag<T: Codable>: Codable {
    public var wrappedValue: T {
        didSet {
            didSet = true
        }
    }
    
    public var didSet = false
    
    public init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
    
    public init(from decoder: any Decoder) throws {
        wrappedValue = try T(from: decoder)
    }
    
    public func encode(to encoder: any Encoder) throws {
        try wrappedValue.encode(to: encoder)
    }
}

Затем «захватите» KeyedEncodingContainer.encode, чтобы, если didSet ложно, ничего не кодировалось:

extension KeyedEncodingContainer {
    mutating func encode<T: Codable>(
        _ value: DidSetFlag<T>,
        forKey key: KeyedEncodingContainer<K>.Key
    ) throws {
        if value.didSet {
            try encode(value.wrappedValue, forKey: key)
        }
    }
}

Синтезированная реализация Codable будет разрешаться в эту реализацию в расширении, если только тип Codable не находится в другом модуле.

Пример использования:

struct Foo: Codable {
    @DidSetFlag var foo = 0
    @DidSetFlag var bar = 0
    
    mutating func resetFlags() {
        _foo.didSet = false
        _bar.didSet = false
    }
}

var foo = Foo()
let encoder = JSONEncoder()
print(String(data: try encoder.encode(foo), encoding:. utf8)!)
foo.foo = 1
print(String(data: try encoder.encode(foo), encoding:. utf8)!)
foo.bar = 2
print(String(data: try encoder.encode(foo), encoding:. utf8)!)

/*
output:

{}
{"foo":1}
{"bar":2,"foo":1}
*/

В качестве бонуса, вот реализация макроса, позволяющая автоматически добавлять @DidSetFlag к каждому свойству, а также генерировать метод resetFlags.

// declaration
@attached(memberAttribute)
@attached(member, names: named(resetFlags))
public macro EncodePropertiesWhenSet() = #externalMacro(module: "Your Module Here...", type: "EncodePropertiesWhenSet")

// implementation
enum EncodePropertiesWhenSet: MemberAttributeMacro, MemberMacro {
    static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
        let hasMutating = !declaration.is(ClassDeclSyntax.self)
        let function = try FunctionDeclSyntax("\(raw: hasMutating ? "mutating " : "")func resetFlags()") {
            for member in declaration.memberBlock.members {
                if let name = name(for: member.decl) {
                    "_\(raw: name).didSet = false"
                }
            }
        }
        return [DeclSyntax(function)]
    }
    
    static func expansion(of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [AttributeSyntax] {
        if name(for: member) != nil {
            ["@DidSetFlag"]
        } else {
            []
        }
    }
    
    static func name(for decl: some DeclSyntaxProtocol) -> String? {
        // filter out computed properties and 'let's
        guard let varDecl = decl.as(VariableDeclSyntax.self),
              varDecl.bindingSpecifier.text == "var",
              varDecl.bindings.count == 1,
              let binding = varDecl.bindings.first,
              binding.accessorBlock == nil,
              let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
        else { return nil }
        return identifier
    }
}


@main
struct MyMacroPlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [
        EncodePropertiesWhenSet.self
    ]
}

// usage:
@EncodePropertiesWhenSet
struct Foo: Codable {
    var foo = 0
    var bar = 0

Другие вопросы по теме