Is there a way to insert additional keys in an Encodable Object in Swift 4?

989 views Asked by At

As the question states: I have a struct conforming to the new Encodable protocol and would like to insert keys and values that are not present as variables or constants in the struct itself. My current workaround is adding them to the struct as constants with the sole purpose of being added to the generated JSON output. Is there a way to accomplish this in func encode(to encoder: Encoder) throws { ... }?


Background Information

I have a rather big Swift 4 project that needs a JSON output to generate statistics and visualizations outside the project itself, so nearly all of my structs, classes and enums conform to the new Encodable protocol. The Output is already working, but the readability is bad at some points. Especially in one struct that describes a field in a rasterized factory layout:

The field has a position (with coordinates x: Int and y: Int) and content of a custom enum FieldType containing the following cases:

case wall
case entrance(robots: Set<Robot>)
case exit(robots: Set<Robot>)
case workstation(object: Workstation)
case robot(object: Robot)
case empty

Position, FieldType, Robot, Workstation and the Field itself already implement Encodable, but the problem is that my JSON output for the fields looks like this:

...
{
    "content" : "empty",
    "position" : {
        "x" : 9,
        "y" : 6
    }
},
{
    "content" : "empty",
    "position" : {
        "x" : 10,
        "y" : 6
    }
},
{
    "content" : {
        "id" : 3,
        "state" : "idle",
        "type" : "C",
        "position" : {
            "x" : 11,
            "y" : 6
        }
    },
    "position" : {
        "x" : 11,
        "y" : 6
    }
},
{
    "content" : "empty",
    "position" : {
        "x" : 12,
        "y" : 6
    }
},
...

As you can see there is something other than an empty field at position (11, 6), but without knowing that only workstations have an idle state in my simulation, there is no way of knowing what content that field holds. So I tried adding a key "object" with the value "workstation" to my workstation model (analogical "robot" in the robot model) by defining CodingKeys as an enum:

private enum CodingKeys: String, CodingKey {
    case object
    case id
    case state
    case type
    case position
}

This results in my struct Workstation no longer conforming to Encodable. So the only solution I found was adding a constant let object = "workstation", but this is a constant with the sole purpose of being added to the output and I would like to avoid that.

1

There are 1 answers

1
Code Different On

You'll have to write a custom encoder for FieldType:

enum FieldType: Encodable {
    case wall
    case entrance(robots: Set<Robot>)
    case exit(robots: Set<Robot>)
    case workstation(object: Workstation)
    case robot(object: Robot)
    case empty

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()

        switch self {
        case .wall:
            try container.encode("wall")
        case .entrance(let robotset):
            try container.encode(robotset)
        case .exit(let robotSet):
            try container.encode(robotSet)
        case .workstation(let workstation):
            try container.encode(workstation)
        case .robot(let robot):
            try container.encode(robot)
        case .empty:
            try container.encode("empty")
        }
    }
}

(This assumes that Robot and Workstation are already Encodable)