Silis Alin
Silis Alin

Reputation: 160

Swift Encodable: encode nil as an empty object

How can I encode a nil property as an empty JSON object?

struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil
}

struct Bar: Encodable {
    let number: Int
}

let data = try! JSONEncoder().encode(Foo())

print(String(data: data, encoding: .utf8)!)

This prints out:

"{"id":7}"

What I want is:

"{"id":7, "bar":{}}"

Upvotes: 2

Views: 2514

Answers (3)

Cristik
Cristik

Reputation: 32785

Not sure why you'd need this, as encoding to a form that fails decoding is not something usually done.

Nonetheless, if you find yourself needing this kind of logic in multiple places, you can extend KeyedEncodingContainer with this kind of functionality:

extension KeyedEncodingContainer {
    mutating func encodeOptional<T: Encodable>(_ value: T?, forKey key: Self.Key) throws {
        if let value = value { try encode(value, forKey: key) }
        else { try encode([String:String](), forKey: key) }
    }
}

, and then implement the encode(to:) method in Foo:

struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil
    
    enum CodingKeys: String, CodingKey {
        case id
        case bar
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encodeOptional(bar, forKey: .bar)
    }
}

You can also extend UnkeyedDecodingContainer, and SingleValueDecodingContainer with similar encodeOptional methods, if you find yourself needing to encode empty JSON objects for nil values in other kind of containers.

Upvotes: 1

SamB
SamB

Reputation: 1710

You can introduce an empty struct with no properties to the encoder, when bar = nil

struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil
    
    enum CodingKeys : String, CodingKey {
        case id
        case bar
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        
        if let bar = bar {
            try container.encode(bar, forKey: .bar)
        }
        else {
            try container.encode(Empty(), forKey: .bar)
        }
    }
}

struct Bar: Encodable {
    let number: Int
}

struct Empty: Encodable {
}

Upvotes: 3

Joakim Danielson
Joakim Danielson

Reputation: 51892

Implement a custom encode(to:) for Foo and use an empty dictionary if Bar is nil

struct Foo: Encodable {
    let id = 10
    let bar: Bar? = nil

    enum CodingKeys: String, CodingKey {
        case id, bar
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        switch bar {
        case .some(let value):
            try container.encode(value, forKey: .bar)
        case .none:
            try container.encode([String: Bar?](), forKey: .bar)
        }
    }
}

Upvotes: 2

Related Questions