Extend JSONDecoder to allow for models to be serialized differently based on endpoint

I have many models that, depending on the endpoint, or serialized differently. My first attempt had a

init(from decoder: Decoder)
riddled with nested
try catch
blocks. I thought a better solution would be to extend
JSONDecoder
so that when I initialize one, I can specify which endpoint i am pulling from. Then in my models
init(from decoder: Decoder)
I could have a
switch
like
switch 
case endpoint1:
x = decoder.decode(Int.self, .x)
case endpoint2:
j = decoder.decode(String.self, .j)

The problem I ran into is that the class you have inside the

init
is a
Decoder
not a
JSONDecoder
. I can't figure out a place that, if I extend
Decoder
and allow myself to specify an endpoint, I could actually specify an endpoint, since
JSONDecoder.decode()
instantiates it's own
Decoder
behind the scenes. Thoughts?
EXAMPLE:

How would you recommend initializing an object from decoder that has say two different variations depending on where you are deserializing it from. For Example
Lets say I get the first book from http://library.com/bookLocations/{id}
And the second book from http://library.com/bookInfo/{id}

Book: { 
  Author: "James" 
  Title: "JamesBook" 
  LibraryId: "387fh384fh3i09" 
  SectionNumber: "870D" 
}


Book: { 
  Author: "James" 
  Title: "JamesBook" 
  Length: "870" 
  Reviews: "9/10" 
}

What is the best way to handle both cases inside


init(from decoder: Decoder)

if I want to use the same model/object for both



struct Book: Codable { 
  var author: String 
  var title: String 
  var libraryId: String? 
  var sectionNumber: String? 
  var length: String? 
  var reviews: String? 
}

Replies

`Decoder` is a protocol where all `Decodable` things rely on.

You should better not implement `init(from decoder: Decoder)` using some extended feature of a specific `Decoder`.


Please show more concrete examples, JSON texts of the endpoints and your model.

How then would you recommend initializing an object from decoder that has say two different variations depending on where you are deserializing it from. For Example
Lets say I get the first book from http://library.com/bookLocations/{id}
And the second book from http://library.com/bookInfo/{id}

Book: {
  Author: "James"
  Title: "JamesBook"
  LibraryId: "387fh384fh3i09"
  SectionNumber: "870D"
}
Book: {
  Author: "James"
  Title: "JamesBook"
  Length: "870"
  Reviews: "9/10"
}

What is the best way to handle both cases inside

init(from decoder: Decoder)

if I want to use the same model/object for both

struct Book: Codable {
  var author: String
  var title: String
  var libraryId: String?
  var sectionNumber: String?
  var length: String?
  var reviews: String?
}

In this simple case as in your example, you have no need to do anything special.

(Please show valid JSON texts, with easily testable code and data, more readers would test them and you would get more responses.)


struct Book: Codable {
    var author: String
    var title: String
    var libraryId: String?
    var sectionNumber: String?
    var length: String?
    var reviews: String?
    
    enum CodingKeys: String, CodingKey {
        case author = "Author"
        case title = "Title"
        case libraryId = "LibraryId"
        case sectionNumber = "SectionNumber"
        case length = "Length"
        case reviews = "Reviews"
    }
}
struct Result: Codable {
    var book: Book
    
    enum CodingKeys: String, CodingKey {
        case book = "Book"
    }
}

let endpoint1Text = """
{
    "Book": {
      "Author": "James",
      "Title": "JamesBook",
      "LibraryId": "387fh384fh3i09",
      "SectionNumber": "870D"
    }
}
"""

let endpoint2Text = """
{
    "Book": {
      "Author": "James",
      "Title": "JamesBook",
      "Length": "870",
      "Reviews": "9/10"
    }
}
"""

do {
    let result1 = try JSONDecoder().decode(Result.self, from: endpoint1Text.data(using: .utf8)!)
    print(result1)
    let result2 = try JSONDecoder().decode(Result.self, from: endpoint2Text.data(using: .utf8)!)
    print(result2)
} catch {
    print(error)
}

Tested in Playgrounds, I get this.

Result(book: __lldb_expr_5.Book(author: "James", title: "JamesBook", libraryId: Optional("387fh384fh3i09"), sectionNumber: Optional("870D"), length: nil, reviews: nil))

Result(book: __lldb_expr_5.Book(author: "James", title: "JamesBook", libraryId: nil, sectionNumber: nil, length: Optional("870"), reviews: Optional("9/10")))


Your actual model may be more complex and there may be some difficulties, but I cannot say any more without seeing it.

Thank you, this was helpful. One other question though, why does this code even run without the

init(from decoder: Decoder)

initializer?

If the type definition does not have an explicit declaration of the initializer, Swift compiler will automatically generate it.