Wrapping Core Data into Codable structs











up vote
0
down vote

favorite












I am wrapping Core Data objects into structs to make them Codable.



[NB: Before your direct me to writing the swift file for each Core Data class, I would like to say that wrapping the NSManagedObject children result from a conscious choice in favour of code maintainability, as the data model may evolve in the future.]



I have the few classes like these, here is an example:



struct CodableNeed : Codable {
enum CodingKeys: String, CodingKey {
...
}

var need:Need

init (_ need:Need) {
self.need = need
}

init(from decoder: Decoder) throws {
....
}

func encode(to encoder: Encoder) throws {
....
}
}


This works actually pretty well, as any update in the init(from:decoder) of the struct is actually stored in the ManagedObjectContext.



In order to let each NSManagedObject class instance return their own struct, I defined a protocol where each class instance may return their own Codable struct:



protocol CodableWhenWrapped {
func wrapToCodable() -> Codable
}

extension Need : CodableWhenWrapped {

func wrapToCodable() -> Codable {
return CodableNeed(self)
}
}


I then use this in an encoding function:



func jsonDataOfCodable<T:Encodable>(_ object:T) throws -> Data {
let encoder = JSONEncoder()
let data = try encoder.encode(object)
return data
}


and I call this function to generate an URLSessionUploadTask :



func updateTaskFor<T: NSManagedObject> (_ object:T, withSession session:URLSession) throws -> URLSessionUploadTask
where T: CodableWhenWrapped
{
let encoder = JSONEncoder()

// Here is the compile error:
// " Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)' "
let jsonData = try jsonDataOfCodable(object.wrapToCodable())

// then continue with generating the uploadTask
let url = "https://myurl.com/"
let request = URLRequest(url: url)
let updateTask = session.uploadTask(with: request, from: jsonData) { (data, response, error) in
....
}
}


Here is the issue: the code does not compile when calling jsonDataOfCodable : Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)'.



Any idea why the compiler does not like this?



Note that I have the same issue when I specify <T:Codable>instead of <T:Encodable> in the jsonDataOfCodable prototype.










share|improve this question






















  • JSONEncoder().encode requires a concrete type conforming to (En)codable, not the protocol Codable itself. You could use an associatedtype. It might be easier to implement init(from decoder: and encode(to encoder: in each NSManagedObject sublcass. Especially if there are relationships it's the only choice.
    – vadian
    2 days ago

















up vote
0
down vote

favorite












I am wrapping Core Data objects into structs to make them Codable.



[NB: Before your direct me to writing the swift file for each Core Data class, I would like to say that wrapping the NSManagedObject children result from a conscious choice in favour of code maintainability, as the data model may evolve in the future.]



I have the few classes like these, here is an example:



struct CodableNeed : Codable {
enum CodingKeys: String, CodingKey {
...
}

var need:Need

init (_ need:Need) {
self.need = need
}

init(from decoder: Decoder) throws {
....
}

func encode(to encoder: Encoder) throws {
....
}
}


This works actually pretty well, as any update in the init(from:decoder) of the struct is actually stored in the ManagedObjectContext.



In order to let each NSManagedObject class instance return their own struct, I defined a protocol where each class instance may return their own Codable struct:



protocol CodableWhenWrapped {
func wrapToCodable() -> Codable
}

extension Need : CodableWhenWrapped {

func wrapToCodable() -> Codable {
return CodableNeed(self)
}
}


I then use this in an encoding function:



func jsonDataOfCodable<T:Encodable>(_ object:T) throws -> Data {
let encoder = JSONEncoder()
let data = try encoder.encode(object)
return data
}


and I call this function to generate an URLSessionUploadTask :



func updateTaskFor<T: NSManagedObject> (_ object:T, withSession session:URLSession) throws -> URLSessionUploadTask
where T: CodableWhenWrapped
{
let encoder = JSONEncoder()

// Here is the compile error:
// " Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)' "
let jsonData = try jsonDataOfCodable(object.wrapToCodable())

// then continue with generating the uploadTask
let url = "https://myurl.com/"
let request = URLRequest(url: url)
let updateTask = session.uploadTask(with: request, from: jsonData) { (data, response, error) in
....
}
}


Here is the issue: the code does not compile when calling jsonDataOfCodable : Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)'.



Any idea why the compiler does not like this?



Note that I have the same issue when I specify <T:Codable>instead of <T:Encodable> in the jsonDataOfCodable prototype.










share|improve this question






















  • JSONEncoder().encode requires a concrete type conforming to (En)codable, not the protocol Codable itself. You could use an associatedtype. It might be easier to implement init(from decoder: and encode(to encoder: in each NSManagedObject sublcass. Especially if there are relationships it's the only choice.
    – vadian
    2 days ago















up vote
0
down vote

favorite









up vote
0
down vote

favorite











I am wrapping Core Data objects into structs to make them Codable.



[NB: Before your direct me to writing the swift file for each Core Data class, I would like to say that wrapping the NSManagedObject children result from a conscious choice in favour of code maintainability, as the data model may evolve in the future.]



I have the few classes like these, here is an example:



struct CodableNeed : Codable {
enum CodingKeys: String, CodingKey {
...
}

var need:Need

init (_ need:Need) {
self.need = need
}

init(from decoder: Decoder) throws {
....
}

func encode(to encoder: Encoder) throws {
....
}
}


This works actually pretty well, as any update in the init(from:decoder) of the struct is actually stored in the ManagedObjectContext.



In order to let each NSManagedObject class instance return their own struct, I defined a protocol where each class instance may return their own Codable struct:



protocol CodableWhenWrapped {
func wrapToCodable() -> Codable
}

extension Need : CodableWhenWrapped {

func wrapToCodable() -> Codable {
return CodableNeed(self)
}
}


I then use this in an encoding function:



func jsonDataOfCodable<T:Encodable>(_ object:T) throws -> Data {
let encoder = JSONEncoder()
let data = try encoder.encode(object)
return data
}


and I call this function to generate an URLSessionUploadTask :



func updateTaskFor<T: NSManagedObject> (_ object:T, withSession session:URLSession) throws -> URLSessionUploadTask
where T: CodableWhenWrapped
{
let encoder = JSONEncoder()

// Here is the compile error:
// " Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)' "
let jsonData = try jsonDataOfCodable(object.wrapToCodable())

// then continue with generating the uploadTask
let url = "https://myurl.com/"
let request = URLRequest(url: url)
let updateTask = session.uploadTask(with: request, from: jsonData) { (data, response, error) in
....
}
}


Here is the issue: the code does not compile when calling jsonDataOfCodable : Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)'.



Any idea why the compiler does not like this?



Note that I have the same issue when I specify <T:Codable>instead of <T:Encodable> in the jsonDataOfCodable prototype.










share|improve this question













I am wrapping Core Data objects into structs to make them Codable.



[NB: Before your direct me to writing the swift file for each Core Data class, I would like to say that wrapping the NSManagedObject children result from a conscious choice in favour of code maintainability, as the data model may evolve in the future.]



I have the few classes like these, here is an example:



struct CodableNeed : Codable {
enum CodingKeys: String, CodingKey {
...
}

var need:Need

init (_ need:Need) {
self.need = need
}

init(from decoder: Decoder) throws {
....
}

func encode(to encoder: Encoder) throws {
....
}
}


This works actually pretty well, as any update in the init(from:decoder) of the struct is actually stored in the ManagedObjectContext.



In order to let each NSManagedObject class instance return their own struct, I defined a protocol where each class instance may return their own Codable struct:



protocol CodableWhenWrapped {
func wrapToCodable() -> Codable
}

extension Need : CodableWhenWrapped {

func wrapToCodable() -> Codable {
return CodableNeed(self)
}
}


I then use this in an encoding function:



func jsonDataOfCodable<T:Encodable>(_ object:T) throws -> Data {
let encoder = JSONEncoder()
let data = try encoder.encode(object)
return data
}


and I call this function to generate an URLSessionUploadTask :



func updateTaskFor<T: NSManagedObject> (_ object:T, withSession session:URLSession) throws -> URLSessionUploadTask
where T: CodableWhenWrapped
{
let encoder = JSONEncoder()

// Here is the compile error:
// " Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)' "
let jsonData = try jsonDataOfCodable(object.wrapToCodable())

// then continue with generating the uploadTask
let url = "https://myurl.com/"
let request = URLRequest(url: url)
let updateTask = session.uploadTask(with: request, from: jsonData) { (data, response, error) in
....
}
}


Here is the issue: the code does not compile when calling jsonDataOfCodable : Cannot invoke 'jsonDataOfCodable' with an argument list of type '(Codable)'.



Any idea why the compiler does not like this?



Note that I have the same issue when I specify <T:Codable>instead of <T:Encodable> in the jsonDataOfCodable prototype.







swift core-data wrapper swift-protocols codable






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked 2 days ago









harrouet

445




445












  • JSONEncoder().encode requires a concrete type conforming to (En)codable, not the protocol Codable itself. You could use an associatedtype. It might be easier to implement init(from decoder: and encode(to encoder: in each NSManagedObject sublcass. Especially if there are relationships it's the only choice.
    – vadian
    2 days ago




















  • JSONEncoder().encode requires a concrete type conforming to (En)codable, not the protocol Codable itself. You could use an associatedtype. It might be easier to implement init(from decoder: and encode(to encoder: in each NSManagedObject sublcass. Especially if there are relationships it's the only choice.
    – vadian
    2 days ago


















JSONEncoder().encode requires a concrete type conforming to (En)codable, not the protocol Codable itself. You could use an associatedtype. It might be easier to implement init(from decoder: and encode(to encoder: in each NSManagedObject sublcass. Especially if there are relationships it's the only choice.
– vadian
2 days ago






JSONEncoder().encode requires a concrete type conforming to (En)codable, not the protocol Codable itself. You could use an associatedtype. It might be easier to implement init(from decoder: and encode(to encoder: in each NSManagedObject sublcass. Especially if there are relationships it's the only choice.
– vadian
2 days ago














1 Answer
1






active

oldest

votes

















up vote
0
down vote













I am answering my own question after reflecting on vadian's comment.



I solved the issue by updating my protocol:



protocol CodableWhenWrapped : Encodable {
func wrapToCodable<T>() -> CodableWrapper<T>
func update(from decoder:Decoder) throws
}


Then I added a Generic wrapper for my objects (all inheriting from a Synchronizable class):



struct CodableWrapper<T> : Codable
where T:CodableWhenWrapped, T: Synchronizable
{
enum SynchronizableCodingKeys: String, CodingKey {
case pk = "idOnServer"
case lastModificationDate = "modified_on"
}

var object:T

init(_ obj:T){
self.object = obj
}

init(from decoder: Decoder) throws {
// This is how the NSManagedObjectContext is passed through the decoder
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext,
let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext
else {
fatalError("Failed to decode Need: could not retriever the NSManagedObjectContext. Was it included in Decoder.userInfo as CodingUserInfoKey.managedObjectContext ?")
}

// check if there is a an existing object with the same idOnServer. If not, create a new one
let container = try decoder.container(keyedBy: SynchronizableCodingKeys.self)
let pk = try container.decode(UUID.self, forKey: .pk)

object = try Synchronizable.withIDOnServer(pk.uuidString, inMOC: managedObjectContext) ?? T(context: managedObjectContext)
try object.update(from: decoder)
}

func encode(to encoder: Encoder) throws {
try object.encode(to: encoder)
}
}


I can then have a generic function to serialize to JSON:



func jsonDataFor<T>(_ object:T) throws -> Data
where T:CodableWhenWrapped, T:Synchronizable {
let encoder = JSONEncoder()
let wrappedObject:CodableWrapper<T> = object.wrapToCodable()
let jsonData = try encoder.encode(wrappedObject)
return jsonData
}


Decoding is just:



let _ = try decoder.decode(CodableWrapper<T>.self, from: data!)


Thanks @vadian for your help, I hope this is useful for others!
BTW, the CodableWhenWrapped protocol can easilt be reused for other objects types.






share|improve this answer





















    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














     

    draft saved


    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53350354%2fwrapping-core-data-into-codable-structs%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    0
    down vote













    I am answering my own question after reflecting on vadian's comment.



    I solved the issue by updating my protocol:



    protocol CodableWhenWrapped : Encodable {
    func wrapToCodable<T>() -> CodableWrapper<T>
    func update(from decoder:Decoder) throws
    }


    Then I added a Generic wrapper for my objects (all inheriting from a Synchronizable class):



    struct CodableWrapper<T> : Codable
    where T:CodableWhenWrapped, T: Synchronizable
    {
    enum SynchronizableCodingKeys: String, CodingKey {
    case pk = "idOnServer"
    case lastModificationDate = "modified_on"
    }

    var object:T

    init(_ obj:T){
    self.object = obj
    }

    init(from decoder: Decoder) throws {
    // This is how the NSManagedObjectContext is passed through the decoder
    guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext,
    let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext
    else {
    fatalError("Failed to decode Need: could not retriever the NSManagedObjectContext. Was it included in Decoder.userInfo as CodingUserInfoKey.managedObjectContext ?")
    }

    // check if there is a an existing object with the same idOnServer. If not, create a new one
    let container = try decoder.container(keyedBy: SynchronizableCodingKeys.self)
    let pk = try container.decode(UUID.self, forKey: .pk)

    object = try Synchronizable.withIDOnServer(pk.uuidString, inMOC: managedObjectContext) ?? T(context: managedObjectContext)
    try object.update(from: decoder)
    }

    func encode(to encoder: Encoder) throws {
    try object.encode(to: encoder)
    }
    }


    I can then have a generic function to serialize to JSON:



    func jsonDataFor<T>(_ object:T) throws -> Data
    where T:CodableWhenWrapped, T:Synchronizable {
    let encoder = JSONEncoder()
    let wrappedObject:CodableWrapper<T> = object.wrapToCodable()
    let jsonData = try encoder.encode(wrappedObject)
    return jsonData
    }


    Decoding is just:



    let _ = try decoder.decode(CodableWrapper<T>.self, from: data!)


    Thanks @vadian for your help, I hope this is useful for others!
    BTW, the CodableWhenWrapped protocol can easilt be reused for other objects types.






    share|improve this answer

























      up vote
      0
      down vote













      I am answering my own question after reflecting on vadian's comment.



      I solved the issue by updating my protocol:



      protocol CodableWhenWrapped : Encodable {
      func wrapToCodable<T>() -> CodableWrapper<T>
      func update(from decoder:Decoder) throws
      }


      Then I added a Generic wrapper for my objects (all inheriting from a Synchronizable class):



      struct CodableWrapper<T> : Codable
      where T:CodableWhenWrapped, T: Synchronizable
      {
      enum SynchronizableCodingKeys: String, CodingKey {
      case pk = "idOnServer"
      case lastModificationDate = "modified_on"
      }

      var object:T

      init(_ obj:T){
      self.object = obj
      }

      init(from decoder: Decoder) throws {
      // This is how the NSManagedObjectContext is passed through the decoder
      guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext,
      let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext
      else {
      fatalError("Failed to decode Need: could not retriever the NSManagedObjectContext. Was it included in Decoder.userInfo as CodingUserInfoKey.managedObjectContext ?")
      }

      // check if there is a an existing object with the same idOnServer. If not, create a new one
      let container = try decoder.container(keyedBy: SynchronizableCodingKeys.self)
      let pk = try container.decode(UUID.self, forKey: .pk)

      object = try Synchronizable.withIDOnServer(pk.uuidString, inMOC: managedObjectContext) ?? T(context: managedObjectContext)
      try object.update(from: decoder)
      }

      func encode(to encoder: Encoder) throws {
      try object.encode(to: encoder)
      }
      }


      I can then have a generic function to serialize to JSON:



      func jsonDataFor<T>(_ object:T) throws -> Data
      where T:CodableWhenWrapped, T:Synchronizable {
      let encoder = JSONEncoder()
      let wrappedObject:CodableWrapper<T> = object.wrapToCodable()
      let jsonData = try encoder.encode(wrappedObject)
      return jsonData
      }


      Decoding is just:



      let _ = try decoder.decode(CodableWrapper<T>.self, from: data!)


      Thanks @vadian for your help, I hope this is useful for others!
      BTW, the CodableWhenWrapped protocol can easilt be reused for other objects types.






      share|improve this answer























        up vote
        0
        down vote










        up vote
        0
        down vote









        I am answering my own question after reflecting on vadian's comment.



        I solved the issue by updating my protocol:



        protocol CodableWhenWrapped : Encodable {
        func wrapToCodable<T>() -> CodableWrapper<T>
        func update(from decoder:Decoder) throws
        }


        Then I added a Generic wrapper for my objects (all inheriting from a Synchronizable class):



        struct CodableWrapper<T> : Codable
        where T:CodableWhenWrapped, T: Synchronizable
        {
        enum SynchronizableCodingKeys: String, CodingKey {
        case pk = "idOnServer"
        case lastModificationDate = "modified_on"
        }

        var object:T

        init(_ obj:T){
        self.object = obj
        }

        init(from decoder: Decoder) throws {
        // This is how the NSManagedObjectContext is passed through the decoder
        guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext,
        let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext
        else {
        fatalError("Failed to decode Need: could not retriever the NSManagedObjectContext. Was it included in Decoder.userInfo as CodingUserInfoKey.managedObjectContext ?")
        }

        // check if there is a an existing object with the same idOnServer. If not, create a new one
        let container = try decoder.container(keyedBy: SynchronizableCodingKeys.self)
        let pk = try container.decode(UUID.self, forKey: .pk)

        object = try Synchronizable.withIDOnServer(pk.uuidString, inMOC: managedObjectContext) ?? T(context: managedObjectContext)
        try object.update(from: decoder)
        }

        func encode(to encoder: Encoder) throws {
        try object.encode(to: encoder)
        }
        }


        I can then have a generic function to serialize to JSON:



        func jsonDataFor<T>(_ object:T) throws -> Data
        where T:CodableWhenWrapped, T:Synchronizable {
        let encoder = JSONEncoder()
        let wrappedObject:CodableWrapper<T> = object.wrapToCodable()
        let jsonData = try encoder.encode(wrappedObject)
        return jsonData
        }


        Decoding is just:



        let _ = try decoder.decode(CodableWrapper<T>.self, from: data!)


        Thanks @vadian for your help, I hope this is useful for others!
        BTW, the CodableWhenWrapped protocol can easilt be reused for other objects types.






        share|improve this answer












        I am answering my own question after reflecting on vadian's comment.



        I solved the issue by updating my protocol:



        protocol CodableWhenWrapped : Encodable {
        func wrapToCodable<T>() -> CodableWrapper<T>
        func update(from decoder:Decoder) throws
        }


        Then I added a Generic wrapper for my objects (all inheriting from a Synchronizable class):



        struct CodableWrapper<T> : Codable
        where T:CodableWhenWrapped, T: Synchronizable
        {
        enum SynchronizableCodingKeys: String, CodingKey {
        case pk = "idOnServer"
        case lastModificationDate = "modified_on"
        }

        var object:T

        init(_ obj:T){
        self.object = obj
        }

        init(from decoder: Decoder) throws {
        // This is how the NSManagedObjectContext is passed through the decoder
        guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.managedObjectContext,
        let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext
        else {
        fatalError("Failed to decode Need: could not retriever the NSManagedObjectContext. Was it included in Decoder.userInfo as CodingUserInfoKey.managedObjectContext ?")
        }

        // check if there is a an existing object with the same idOnServer. If not, create a new one
        let container = try decoder.container(keyedBy: SynchronizableCodingKeys.self)
        let pk = try container.decode(UUID.self, forKey: .pk)

        object = try Synchronizable.withIDOnServer(pk.uuidString, inMOC: managedObjectContext) ?? T(context: managedObjectContext)
        try object.update(from: decoder)
        }

        func encode(to encoder: Encoder) throws {
        try object.encode(to: encoder)
        }
        }


        I can then have a generic function to serialize to JSON:



        func jsonDataFor<T>(_ object:T) throws -> Data
        where T:CodableWhenWrapped, T:Synchronizable {
        let encoder = JSONEncoder()
        let wrappedObject:CodableWrapper<T> = object.wrapToCodable()
        let jsonData = try encoder.encode(wrappedObject)
        return jsonData
        }


        Decoding is just:



        let _ = try decoder.decode(CodableWrapper<T>.self, from: data!)


        Thanks @vadian for your help, I hope this is useful for others!
        BTW, the CodableWhenWrapped protocol can easilt be reused for other objects types.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered 2 days ago









        harrouet

        445




        445






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53350354%2fwrapping-core-data-into-codable-structs%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Costa Masnaga

            Fotorealismo

            Sidney Franklin