Returning One Component of Struct as Encoded Value in JSON

I have a class that I want to custom encode into JSON:

class Declination: Decodable, Encodable {
    var asString: String
    var asDouble: Double

init(_ asString: String) {
        self.asString = asString
        self.asDouble = raToDouble(asString)
    }
    
    required init(from decoder: Decoder) throws {
        let value = try decoder.singleValueContainer()
        self.asString = try value.decode(String.self)
        self.asDouble = declinationToDouble(asString)
    }
}

As you can see, I calculate the double form of the declination when I decode a JSON file containing the data. What I want to do now is ENCODE the class back out as a single string.

Currently the standard JSON encode in Swift produces the following:

"declination":{"asDouble":18.26388888888889,"asString":"+18:15:50.00"}

what I want to produce is:

declination:"+18:15:50.00"

How can I easily do that? I've read up about custom encoders and such, and I get confused about the containers and what keys are being used. I think there might be a simple answer where I could just code:

extension Coordinate: Encodable {
    func encode(to encoder: Encoder) throws {
        return encoder.encode(self.asString)
    }
}

But experienced Swift developers will immediately see that won't work. Should I do JSONSerialization instead? Can I just write a toString() extension and have JSON pick that up?

Any help would be appreciated.

Thanks, Robert

Answered by Scott in 822506022

You are close. The custom encode(to:) just needs to be the inverse operation of init(from:) (using a single value container) like this:

func encode(to encoder: Encoder) throws {
    var c = encoder.singleValueContainer()
    try c.encode(asString)
}

Should I do JSONSerialization instead?

Nope! That’s a huge step backwards from the nice Swift Codable API.

BTW, would it be feasible to make asDouble be a computed property, derived from asString as needed? Or the other way around? Ideally only one of them would be a stored property to serve as the single source of truth.

You are close. The custom encode(to:) just needs to be the inverse operation of init(from:) (using a single value container) like this:

func encode(to encoder: Encoder) throws {
    var c = encoder.singleValueContainer()
    try c.encode(asString)
}

Should I do JSONSerialization instead?

Nope! That’s a huge step backwards from the nice Swift Codable API.

BTW, would it be feasible to make asDouble be a computed property, derived from asString as needed? Or the other way around? Ideally only one of them would be a stored property to serve as the single source of truth.

Returning One Component of Struct as Encoded Value in JSON
 
 
Q