0

I've got an idea that I'm trying to test out, I want to be able to have an array of different objects that are all Codable.

Here is the json

{
  "cells": 
  [
    { 
      "header": "dummy header"
    },
    {
      "title": "dummy title"
    }
  ]
}

Also a picture from Firestore because I'm not sure if I wrote that json out correctly: Firestore mode

Here's what I had so far testing with generics

struct Submission<Cell: Codable>: Codable {
    let cells: [Cell]
}

struct ChecklistCell: Codable {
    let header: String
}

struct SegmentedCell: Codable {
    let title: String
}

The overarching goal is to decode a document that has an array (of cells) that can be different types, but are all codable. I'm not sure if this is possible, or if there is an even better approach. Thanks.

Update: Update I did @Fogmeister 's solution and got it working, but not the most desirable outcome. It adds a weird layer to the json that ideally wouldn't be there. Any ideas?

6
  • 1
    You haven’t really worded a question but let me just point out that any type conforms to Codable if all of its containing types conform to Codable Commented Oct 8, 2021 at 7:57
  • @JoakimDanielson It sounds like you are saying that if i have a struct with a string type inside it, that struct is automatically codable? "any type conforms to Codable if all of its containing types conform to Codable" Commented Oct 8, 2021 at 8:07
  • Yes if you declare it to conform to Codable Commented Oct 8, 2021 at 8:41
  • 1
    Consider to think the other way round and send Codable compliant data structures. Heterogenous arrays are not supported in Swift by default. With your code you cannot decode ChecklistCell and SegmentedCell simultaneously. The synthesized implementation supports only homogenous arrays. Commented Oct 8, 2021 at 8:56
  • @vadian Thanks, I think that clears it up, I had a feeling it wasn't possible, kept running into dead ends. Commented Oct 8, 2021 at 9:36

1 Answer 1

1

I have done something similar to this in the past. Not with Firestore (although, more recently I did) but with our CMS that we use.

As @vadian pointed out, heterogeneous arrays are not supported by Swift.

Also... something else to point out.

When you have a generic type defined like...

struct Submission<Cell> {
  let cells: [Cell]
}

Then, by definition, cells is a homogeneous array of a single type. If you try to put different types into it it will not compile.

You can get around this though by using an enum to bundle all your different Cells into a single type.

enum CellTypes {
  case checkList(CheckListCell)
  case segmented(SegmentedCell)
}

Now your array would be a homogeneous array of [CellTypes] where each element would be a case of the enum which would then contain the model of the cell inside it.

struct Submission {
  let cells: [CellTypes]
}

This takes some custom decoding to get straight from JSON but I can't add that right now. If you need some guidance on that I'll update the answer.

Encoding and Decoding

Something to note from a JSON point of view. Your app will need to know which type of cell is being encoded/decoded. So your original JSON schema will need some updating to add this.

The automatic update from Firestore that you have shown is a fairly common way of doing this...

The JSON looks a bit like this...

{
  "cells": 
  [
    {
      "checkListCell": { 
        "header": "dummy header"
      }
    },
    {
      "segmentedCell": {
        "title": "dummy title"
      }
    }
  ]
}

Essentially, each item in the array is now an object that has a single key. From checkListCell, segmentedCell. This will be from any of the cases of your enum. This key tells your app which type of cell the object is.

Then the object shown against that key is then the underlying cell itself.

This is probably the cleanest way of modelling this data.

So, you might have two checklist cells and then a segmented cell and finally another checklist cell.

This will look like...

{
  "cells": 
  [
    {
      "checkListCell": { 
        "header": "First checklist"
      }
    },
    {
      "checkListCell": { 
        "header": "Second checklist"
      }
    },
    {
      "segmentedCell": {
        "title": "Some segmented stuff"
      }
    },
    {
      "checkListCell": { 
        "header": "Another checklist"
      }
    },
  ]
}

The important thing to think when analysing this JSON is not that it's harder for you (as a human being) to read. But that it's required, and actually fairly easy, for your app to read and decode/encode.

Hope that makes sense.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks! Didn't think to use enums with different cases, perfect!
I added an update if you could check that out, I think I do need help with encoding and decoding...
Just working on an update. :D
@EricE it's a bit tricky as your original JSON structure will need to change in order to work with this. As you would need to know which type of cell each entry is. At the moment there is no type information.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.