Use decodable for custom json decoding - json

I have a json in such format:
{
"route":{
"1":"Atrakcyjno\u015b\u0107 przyrodnicza",
"2":"Atrakcyjno\u015b\u0107 kulturowa",
"3":"Dla rodzin z dzie\u0107mi",
"5":"Dla senior\u00f3w",
"6":"Dla or\u0142\u00f3w",
"8":"Niepe\u0142nosprawni"
},
"apartments":{
"1":"WifI",
"4":"Gastronomia",
"5":"Parking",
"6":"Dla niepe\u0142nosprawnych",
"7":"Dla rodzin z dzie\u0107mi",
"8":"Dla senior\u00f3w"
},
"levels":{
"1":"\u0141atwy",
"2":"\u015aredni",
"3":"Trudny",
"4":"Bardzo trudny"
}
}
I would like to decode it as simple as possible, but I don't know how to decode these sub dictionaries. These are dicts, but it should be array instead. Can I somehow write something, that will make it decode in special way, so that I'll get arrays? So far I have something like this:
struct PreferencesList: Decodable {
private enum CodingKeys: String, CodingKey {
case routes = "route"
case apartments
case levels
}
let routes: [Preference]
let apartments: [Preference]
let levels: [Preference]
}
struct Preference: Decodable {
let id: Int
let name: String
}

I guess you need to do this step by step.
Keep your struct like it is. Preference doesn't need to be Decodable. Then, override init(from decoder: Decoder) throws function like this.
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let routes = try container.decode([String: String].self, forKey: .routes)
self.routes = []
for (key, value) in routes {
self.routes.append(Preference(id: key, name: value))
}
// Do the same for other var...
}
I hope it helps.

Related

Codable decoding values in JSON converting snake case to camel

When using Swift Codable you can specify a keyDecodingStrategy that will convert snake_case to camelCase. This works great for keys in a dictionary, but is there any solution for decoding values in a dictionary in a similarly clean way?
I have an enumeration that is used as a key in one place and as a value in another:
enum Foo: String, Codable, CodingKey, CaseIterable {
case bar
case bazQux // baz_qux in JSON, say
}
Then this is used as a key like so:
struct MyStruct: Codable {
enum Keys: String, CodingKey {
case myKey
}
let myProperty: [Bool]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Foo.self)
myProperty = Foo.allCases.map {
try container.decode(Bool.self, forKey: $0)
}
}
}
But it is also used as a value like so:
struct Buz: Decodable {
enum CodingKeys: String, CodingKey {
case foo
}
// Note this is called using a decoder with keyDecodingStrategy = .convertFromSnakeCase
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let foo = try container.decode(Foo.self, forKey: .foo)
}
}
The JSON for example might include a line like this:
"foo": "baz_qux"
The value decoding only works if I set the raw value of the bazQux case to be baz_qux. But then that breaks the key decoding. It would also be annoying to have to make two separate enums for the same thing just to avoid this problem.
So I can then initialize the right model corresponding to the value.
I'd also like to avoid employing a "hacky" solution… Is there something reasonably elegant that works well with Codable?
Wound up adding a custom decoding initializer on Foo:
public init(from decoder: Decoder) throws {
let rawValue = try decoder.singleValueContainer().decode(String.self)
if let foo = Self.init(rawValue: rawValue.snakeToCamelCase) {
self = foo
} else {
throw CodingError.unknownValue
}
}
snakeToCamelCase is a simple extension method on String I added locally. Plus need to add the enum CodingError: Error to Foo if you want some error handling.
This isn't ideal but at least it's not too complicated. I would rather rely on built-in case conversion methods but I don't see a way to do that here.

How should I decode a json object using JSONDecoder if I am unsure of the keys

I have an api response in the following shape -
{
"textEntries":{
"summary":{
"id":"101e9136-efd9-469e-9848-132023d51fb1",
"text":"some text",
"locale":"en_GB"
},
"body":{
"id":"3692b0ec-5b92-4ab1-bc25-7711499901c5",
"text":"some other text",
"locale":"en_GB"
},
"title":{
"id":"45595d27-7e06-491e-890b-f50a5af1cdfe",
"text":"some more text again",
"locale":"en_GB"
}
}
}
I'd like to decode this via JSONDecoder so I can use the properties. The challenge I have is the keys, in this case summary,body and title are generated elsewhere and not always these values, they are always unique, but are based on logic that takes place elsewhere in the product, so another call for a different content article could return leftBody or subTitle etc.
The model for the body of these props is always the same however, I can expect the same fields to exist on any combination of responses.
I will need to be able to access the body of each key in code elsewhere. Another API response will tell me the key I need though.
I am not sure how I can handle this with Decodable as I cannot type the values ahead of time.
I had considered something like modelling the body -
struct ContentArticleTextEntries: Decodable {
var id: String
var text: String
var locale: Locale
}
and storing the values in a struct like -
struct ContentArticle: Decodable {
var textEntries: [String: ContentArticleTextEntries]
private enum CodingKeys: String, CodingKey {
case textEntries
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.textEntries = try values.decode(ContentArticleTextEntries.self, forKey: .textEntries)
}
}
I could them maybe use a subscript elsewhere to access property however I do not know how to decode into this shape as the above would not work.
So I would later access like textEntries["body"] for example.
I also do no know if there is a better way to handle this.
I had considered converting the keys to a 'type' using an enum, but again not knowing the enum cases ahead of time makes this impossible.
I know textEntries this does not change and I know id, text and locale this does not change. It is the keys in between this layer I do not know. I have tried the helpful solution posted by #vadian but cannot seem to make this work in the context of only needing 1 set of keys decoded.
For the proposed solution in this answer the structs are
struct ContentArticleTextEntries: Decodable {
let title : String
let id: String
let text: String
let locale: Locale
enum CodingKeys: String, CodingKey {
case id, text, locale
}
init(from decoder: Decoder) throws {
self.title = try decoder.currentTitle()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.text = try container.decode(String.self, forKey: .text)
let localeIdentifier = try container.decode(String.self, forKey: .locale)
self.locale = Locale(identifier: localeIdentifier)
}
}
struct ContentArticle: TitleDecodable {
let title : String
var elements: [ContentArticleTextEntries]
}
struct Container: Decodable {
let containers: [ContentArticle]
init(from decoder: Decoder) throws {
self.containers = try decoder.decodeTitledElements(ContentArticle.self)
}
}
Then decode Container.self
If your models are like,
struct ContentArticle: Decodable {
let textEntries: [String: ContentArticleTextEntries]
}
struct ContentArticleTextEntries: Decodable {
var id: String
var text: String
var locale: String
}
Then, you can simply access the data based on key like,
let response = try JSONDecoder().decode(ContentArticle.self, from: data)
let key = "summary"
print(response.textEntries[key])
Note: No need to write enum CodingKeys and init(from:) if there is no special handling while parsing the JSON.
Use "decodeIfPresent" variant method instead of decode method also you need to breakdown the ContentArticleTextEntries dictionary into individual keys:
struct ContentArticle: Decodable {
var id: String
var text: String?
var locale: String?
private enum CodingKeys: String, CodingKey {
case id, text, locale
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
self.text = try container.decodeIfPresent(String.self, forKey: .text)
self.locale = try container.decodeIfPresent(String.self, forKey: .locale)
}
}

Swift - Have an unknown case for enum decoded from json string that is not included in cases [duplicate]

This question already has answers here:
Codable enum with default case in Swift 4
(10 answers)
Closed 3 years ago.
For a given JSON like below:
{
"store": {
"animals": [
{
"type": "dog"
},
{
"type": "cat"
}
]
}
}
I can parse it with enum for type like following:
final class AnimalStore: Decodable {
let store: Store
}
extension AnimalStore {
struct Store: Decodable {
let animals: [Animal]
}
}
extension AnimalStore.Store {
struct Animal: Decodable {
let type: AnimalType?
}
}
extension AnimalStore.Store.Animal {
enum AnimalType: String, Decodable {
case dog = "dog"
case cat = "cat"
//case unknown = how such a case could be implemented?
}
}
And also since it is optional; it would work fine if type key value pair would be missing from json.
But I would like to have another case, lets call it unknown so that if any given type is not dog or cat (string being something else),type would be initialised as unknown. Right now it crashes if a type, other than dog or cat is given.
How initialising with another type other than the ones given can be implemented with enum?
In other words, for a given type like: "type": "bird" I would like type to be initialised as unknown.
Add the enum case with a string, you may as well use "unknown".
To convert non-matching strings to unknowns, you have to manually implement init(from decoder: Decoder) at some point, either in your Animal or in AnimalType. I'd favour using AnimalType so that you don't have to manually decode any of the other properties of Animal.
enum AnimalType: String, Decodable {
case dog = "dog"
case cat = "cat"
case unknown = "unknown"
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
self = AnimalType(rawValue: string) ?? .unknown
}
}
If you did it in Animal, you'd need something like:
// Decode everything else...
type = try? decoder.decode(AnimalType.self, forKey: .type) ?? .unknown
If you want to allow some alternative to your enum values you may use something like this:
enum Alt<S, A> {
case standard(S)
case alternative(A)
}
extension Alt: Decodable where S: Decodable, A: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let standard = try? container.decode(S.self) {
self = .standard(standard)
} else if let alternative = try? container.decode(A.self) {
self = .alternative(alternative)
} else {
throw DecodingError.typeMismatch(
Self.self,
DecodingError.Context(codingPath: container.codingPath, debugDescription: "")
)
}
}
}
And then change AnimalStore.Store.Animal declaration to this:
extension AnimalStore.Store {
struct Animal: Decodable {
let type: Alt<AnimalType, String>?
}
}
Now it will try to decode it as AnimalType first and then, if fails will decode it as alternative type. So you can keep the value of the strings not in your enum.
EDIT: Or in situations when alternative is RawValue of standard you may use something like this:
enum RawBox<T>: RawRepresentable where T: RawRepresentable {
typealias RawValue = T.RawValue
case packed(T)
case raw(RawValue)
init(rawValue: Self.RawValue) {
if let packed = T(rawValue: rawValue) {
self = .packed(packed)
} else {
self = .raw(rawValue)
}
}
var rawValue: T.RawValue {
switch self {
case .packed(let packed):
return packed.rawValue
case .raw(let raw):
return raw
}
}
}
extension RawBox: Decodable where RawValue: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let raw = try container.decode(RawValue.self)
self.init(rawValue: raw)
}
}
extension RawBox: Encodable where RawValue: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.rawValue)
}
}
…
extension AnimalStore.Store {
struct Animal: Decodable {
let type: RawBox<AnimalType>?
}
}
I think you can try with this
extension AnimalStore.Store {
struct Animal: Decodable {
let type: AnimalType?
enum CodingKeys: String, CodingKey {
case type
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try? values.decode(AnimalType.self, forKey: .type) ?? .unknown
}
}
}
extension AnimalStore.Store.Animal {
enum AnimalType: String {
case dog
case cat
case unknown
}
}

Swift 4 Decodable multiple containers

I'm trying to understand how could I parse this multiple container JSON to an object. I've tried this approach (Mark answer), but he explain how to solve it using one-level container. For some reason I can't mimic the behaviour for multiple containers.
{
"graphql": {
"shortcode_media": {
"id": "1657677004214306744",
"shortcode": "BcBQHPchwe4"
}
}
}
class Post: Decodable {
enum CodingKeys: String, CodingKey {
case graphql // The top level "user" key
case shortcode_media
}
enum PostKeys: String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let post = try values.nestedContainer(keyedBy: PostKeys.self, forKey: .shortcode_media)
self.id = try post.decode(String.self, forKey: .id)
}
var id: String
}
I'm getting:
Swift.DecodingError.Context(codingPath: [], debugDescription: "Cannot get KeyedDecodingContainer<PostKeys> -- no value found for key \"shortcode_media\"", underlyingError: nil))
Any help will be much appreciated, thank you!
As vadian notes, you haven't matched the JSON structure. There is no shortcode_media key at the top level like you've encoded in CodingKeys.
In order to decode this with a custom decoder, you will need to walk through each level and deal with it.
class Post: Decodable {
enum CodingKeys: String, CodingKey {
case graphql
}
enum GraphQLKeys: String, CodingKey {
case shortcode_media
}
enum PostKeys: String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
// unload the top level
let container = try decoder.container(keyedBy: CodingKeys.self)
// Unload the graphql key
let graphql = try container.nestedContainer(keyedBy: GraphQLKeys.self, forKey: .graphql)
// unload the shortcode_media key
let post = try graphql.nestedContainer(keyedBy: PostKeys.self, forKey: .shortcode_media)
// Finally, unload the actual object
self.id = try post.decode(String.self, forKey: .id)
}
var id: String
}
Please read the JSON.
Any opening { is quasi a separator. The indentation of the JSON indicates also the hierarchy.
For clarity I removed all coding keys and left the variable names – which should be camelCased – unchanged.
struct Root : Decodable {
let graphql : Graph
// to access the `Media` object declare a lazy instantiated property
lazy var media : Media = {
return graphql.shortcode_media
}()
}
struct Graph : Decodable {
let shortcode_media : Media
}
struct Media : Decodable {
let id: String
let shortcode : String
}
let jsonString = """
{
"graphql": {
"shortcode_media": {
"id": "1657677004214306744",
"shortcode": "BcBQHPchwe4"
}
}
}
"""
do {
let data = Data(jsonString.utf8)
var result = try decoder.decode(Root.self, from: data)
print(result.media)
} catch {
print("error: ", error)
}
Writing a custom initializer with nestedContainer is more effort than creating the actual hierarchy.
Please paste the entire code in a Playground and check it out.

Swift structures: handling multiple types for a single property

I am using Swift 4 and trying to parse some JSON data which apparently in some cases can have different type values for the same key, e.g.:
{
"type": 0.0
}
and
{
"type": "12.44591406"
}
I am actually stuck with defining my struct because I cannot figure out how to handle this case because
struct ItemRaw: Codable {
let parentType: String
enum CodingKeys: String, CodingKey {
case parentType = "type"
}
}
throws "Expected to decode String but found a number instead.", and naturally,
struct ItemRaw: Codable {
let parentType: Float
enum CodingKeys: String, CodingKey {
case parentType = "type"
}
}
throws "Expected to decode Float but found a string/data instead." accordingly.
How can I handle this (and similar) cases when defining my struct?
I ran into the same issue when trying to decode/encode the "edited" field on a Reddit Listing JSON response. I created a struct that represents the dynamic type that could exist for the given key. The key can have either a boolean or an integer.
{ "edited": false }
{ "edited": 123456 }
If you only need to be able to decode, just implement init(from:). If you need to go both ways, you will need to implement encode(to:) function.
struct Edited: Codable {
let isEdited: Bool
let editedTime: Int
// Where we determine what type the value is
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Check for a boolean
do {
isEdited = try container.decode(Bool.self)
editedTime = 0
} catch {
// Check for an integer
editedTime = try container.decode(Int.self)
isEdited = true
}
}
// We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try isEdited ? container.encode(editedTime) : container.encode(false)
}
}
Inside my Codable class, I then use my struct.
struct Listing: Codable {
let edited: Edited
}
Edit: A more specific solution for your scenario
I recommend using the CodingKey protocol and an enum to store all the properties when decoding. When you create something that conforms to Codable the compiler will create a private enum CodingKeys for you. This lets you decide on what to do based on the JSON Object property key.
Just for example, this is the JSON I am decoding:
{"type": "1.234"}
{"type": 1.234}
If you want to cast from a String to a Double because you only want the double value, just decode the string and then create a double from it. (This is what Itai Ferber is doing, you would then have to decode all properties as well using try decoder.decode(type:forKey:))
struct JSONObjectCasted: Codable {
let type: Double?
init(from decoder: Decoder) throws {
// Decode all fields and store them
let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum.
// First check for a Double
do {
type = try container.decode(Double.self, forKey: .type)
} catch {
// The check for a String and then cast it, this will throw if decoding fails
if let typeValue = Double(try container.decode(String.self, forKey: .type)) {
type = typeValue
} else {
// You may want to throw here if you don't want to default the value(in the case that it you can't have an optional).
type = nil
}
}
// Perform other decoding for other properties.
}
}
If you need to store the type along with the value, you can use an enum that conforms to Codable instead of the struct. You could then just use a switch statement with the "type" property of JSONObjectCustomEnum and perform actions based upon the case.
struct JSONObjectCustomEnum: Codable {
let type: DynamicJSONProperty
}
// Where I can represent all the types that the JSON property can be.
enum DynamicJSONProperty: Codable {
case double(Double)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Decode the double
do {
let doubleVal = try container.decode(Double.self)
self = .double(doubleVal)
} catch DecodingError.typeMismatch {
// Decode the string
let stringVal = try container.decode(String.self)
self = .string(stringVal)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
One simple solution is to provide an implementation of init(from:) which attempts to decode the value as a String, and if that fails because the type is wrong, attempt to decode as a Double:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.parentType = try container.decode(String.self, forKey: .parentType)
} catch DecodingError.typeMismatch {
let value = try container.decode(Double.self, forKey: .parentType)
self.parentType = "\(value)"
}
}
I had to decode PHP/MySQL/PDO double value that is given as an String, for this use-case I had to extend the KeyedDecodingContainer, like so:
extension KeyedDecodingContainer {
func decode(forKey key: KeyedDecodingContainer.Key) throws -> Double {
do {
let str = try self.decode(String.self, forKey: key)
if let dbl = Double(str) {
return dbl
}
} catch DecodingError.typeMismatch {
return try self.decode(Double.self, forKey: key)
}
let context = DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Wrong Money Value")
throw DecodingError.typeMismatch(Double.self, context)
}
}
Usage:
let data = """
{"value":"1.2"}
""".data(using: .utf8)!
struct Test: Decodable {
let value: Double
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
self.value = try decoder.container(keyedBy: CodingKeys.self)
.decode(forKey: CodingKeys.value)
}
}
try JSONDecoder().decode(Test.self, from: data).value
// Out Put Json
{
"software_id": "10",
"name": "Kroll"
},
{
"software_id": 580,
"name": "Synmed"
}
// Codable Struct
struct SoftwareDataModel: Codable {
var softwareId:MyValue?
var name:String?
enum CodingKeys: String, CodingKey{
case softwareId = "software_id"
case name
}
}
MYValue is Codable Struct Which help to to convert your datatype into "String" here I mentions only String and Int datatypes.
enum MyValue: Codable {
case string(String)
var stringValue: String? {
switch self {
case .string(let s):
return s
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(Int.self) {
self = .string("\(x)")
return
}
throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let x):
try container.encode(x)
}
}
}
// How to get software_id ?
let softwareId = Struct_object.softwareId?.stringValue ?? "0"