Reputation: 2981
I have a class in Swift whose structure resembles this:
class MyClass {
var name: String
var data: String
}
Which could be initialised where data
contains a JSON object encoded as a String.
var instance = MyClass()
instance.name = "foo"
instance.data = "{\"bar\": \"baz\"}"
I'd now like to serialise this instance using JSONEncoder
, I'd get an output similar to this:
{
"name": "foo",
"data": "{\"bar\": \"baz\"}"
}
However, what I'd really like
{
"name": "foo",
"data": {
"bar": "baz"
}
}
Can I achieve this with JSONEncoder? (without changing the data
type away from String
)
Upvotes: 3
Views: 1787
Reputation: 299305
You'll first need to decode data
as generic JSON. That's a bit tedious, but not too difficult. See RNJSON for a version I wrote, or here's a stripped-down version that handles your issues.
enum JSON: Codable {
struct Key: CodingKey, Hashable {
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
case string(String)
case number(Double) // FIXME: Split Int and Double
case object([Key: JSON])
case array([JSON])
case bool(Bool)
case null
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) }
else if let number = try? decoder.singleValueContainer().decode(Double.self) { self = .number(number) }
else if let object = try? decoder.container(keyedBy: Key.self) {
var result: [Key: JSON] = [:]
for key in object.allKeys {
result[key] = (try? object.decode(JSON.self, forKey: key)) ?? .null
}
self = .object(result)
}
else if var array = try? decoder.unkeyedContainer() {
var result: [JSON] = []
for _ in 0..<(array.count ?? 0) {
result.append(try array.decode(JSON.self))
}
self = .array(result)
}
else if let bool = try? decoder.singleValueContainer().decode(Bool.self) { self = .bool(bool) }
else if let isNull = try? decoder.singleValueContainer().decodeNil(), isNull { self = .null }
else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [],
debugDescription: "Unknown JSON type")) }
}
func encode(to encoder: Encoder) throws {
switch self {
case .string(let string):
var container = encoder.singleValueContainer()
try container.encode(string)
case .number(let number):
var container = encoder.singleValueContainer()
try container.encode(number)
case .bool(let bool):
var container = encoder.singleValueContainer()
try container.encode(bool)
case .object(let object):
var container = encoder.container(keyedBy: Key.self)
for (key, value) in object {
try container.encode(value, forKey: key)
}
case .array(let array):
var container = encoder.unkeyedContainer()
for value in array {
try container.encode(value)
}
case .null:
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
}
With that, you can decode the JSON and then re-encode it:
extension MyClass: Encodable {
enum CodingKeys: CodingKey {
case name, data
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
let json = try JSONDecoder().decode(JSON.self, from: Data(data.utf8))
try container.encode(json, forKey: .data)
}
}
Upvotes: 3
Reputation: 1652
You could use something like this:
extension MyClass {
func jsonFormatted() throws -> String? {
guard let data = data.data(using: .utf8) else {
return nil
}
let anyData = try JSONSerialization.jsonObject(with: data, options: [])
let dictionary = ["name": name, "data": anyData] as [String : Any]
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
let jsonString = String(data: jsonData, encoding: .utf8)
return jsonString
}
}
So basically, you leave the structure of data
intact, but the rest is wrapped in a dictionary that can be converted to that json string you want to achieve.
Note you'll need to handle the optional and errors that could be thrown.
You can use this to test:
if let jsonString = try? instance.jsonFormatted() {
print(jsonString)
}
Upvotes: 1