Load flat data from server into SwiftData @Model with Relationships [closed]

Working with Swiftdata in the format of one model, everything is fine, there is a Transaction model, there is an Account model, they are not directly connected, but have a logical connection in the form of Accountid – ID

@Model final class Transaction: Decodable, Identifiable {
    
    @Attribute(.unique) var id: UInt32
    var accountFromID: UInt32
    var accountToID: UInt32
    var amountFrom: Decimal
    var amountTo: Decimal
    
    init(...) {...}
    
    private enum CodingKeys: CodingKey { ... }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UInt32.self, forKey: .id)
        accountFromID = try container.decode(UInt32.self, forKey: .accountFromID)
        accountToID = try container.decode(UInt32.self, forKey: .accountToID)
        amountFrom = try container.decode(Decimal.self, forKey: .amountFrom)
        amountTo = try container.decode(Decimal.self, forKey: .amountTo)
    }
}

@Model final class Account: Decodable, Identifiable {
    
    @Attribute(.unique) var id: UInt32
    var remainder: Decimal
    
    init( ... ) { ... }

    private enum CodingKeys: String, CodingKey { ... }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UInt32.self, forKey: .id)
        remainder = try container.decode(Decimal.self, forKey: .remainder)
    }
}

What I want to do:

@Model class Transaction: Decodable, Identifiable {
    
    @Attribute(.unique) var id: UInt32
    var accountFrom: Account
    var accountTo: Account
    var amountFrom: Decimal
    var amountTo: Decimal
    ...
}

But I absolutely do not understand how I can turn two different JSON into a voluminous model

Transaction JSON server response:

[
  {
    "id": 1,
    "accountFromID": 1,
    "accountToID": 2,
    "amountFrom": 1.3,
    "amountTo": 1.3
  },
  ...
]

Account JSON server response:

[
  {
    "id": 1,
    "remainder": 100
  },
  ...
]

I need to do something like such an answer from the server?

[
  {
    "id": 1,
    "accountFrom": {
      "id": 1,
      "remainder": 100
    },
    "accountTo": {
      "id": 2,
      "remainder": 90
    },
    "amountFrom": 1.3,
    "amountTo": 1.3
  },
  ...
]

In this case, there will be a lot of duplicated accounts, which can shoot in the leg

Or general response with all info?

{
  "transactions": [
    {
      "id": 1,
      "accountFromID": 1,
      "accountTo": 2,
      "amountFrom": 1.3,
      "amountTo": 1.3
    },
    ...
  ],
  "accounts": [
    {
      "id": 1,
      "remainder": 100
    },
    ...
  ]
}

This option seems to be the most optimal, in it we separately decode different parts of JSON and can connect models by identifiers, but maybe there is a better way?

  • Your ids should match the “unique” Attribute. If you do that swift data with update/create as needed. Manage the accounts and transactions as relationships where the two different models meet.

    – 




  • @loremipsum That is, where the Transactions and Accounts are used, to do [int: account] and compare the dictionary instead of using the binding in Swiftdata? Now it is realized, only in those places where we need to compare twice Accounts, for everyone else Accountgrounds and Currencies. I thought that if these data will immediately turn out at Query, then this will greatly accelerate the interface, since it is not necessary to get data from the hash of the table 6 times on one transaction (which seems to happen for O (1), but in fact this happens with slowing down interface)

    – 

  • You don’t need a dictionary or a Binding. You need a Relationship.

    – 

This is how I would approach it:

Add the server side id keys as separate attributes in Transaction and Account. Something like

var externalId: Int

This can then be used to connect transactions and accounts but it might also be relevant to store the original id for troubleshooting etc.

Then implement Account and Transaction as you want them

@Model class Transaction: Decodable, Identifiable {    
    var accountFrom: Account
    var accountTo: Account
    var amountFrom: Decimal
    var amountTo: Decimal
    
    var externalId: Int
    ...
}

Then I would first decode and import the accounts json and then when decoding and importing the transactions I would either have an account cache, [Int: Account], where the key is the external id or create a specific fetch with a predicate using the external id depending on the amount of Account objects.

When importing the transactions you can then decode the id properties to Int values and use the cache or fetch function from inside init(from:)

let accountFromID = try container.decode(Int.self, forKey: .accountFromID)
if let account = accountCache[accountFromID] {
    accountFrom = account
} else {
     //error handling. Throw error?
}

Leave a Comment