Note that the decode() method needs to know the type of the result instance.

In contrast to JSONSerialization which understands JSON types such as strings and numbers and arrays and dictionaries, JSONDecoder instantiates anything that’s Decodable — that’s how you can decode JSON directly to your own types.

Decoder

As with encoding, JSONDecoder is the class with a friendly interface and handy decode() method called above. What does that method look like?

// class JSONDecoderopenfuncdecode<T:Decodable>(_type:T.Type,fromdata:Data)throws->T{lettopLevel:Anydo{topLevel=tryJSONSerialization.jsonObject(with:data)}catch{throwDecodingError.dataCorrupted(DecodingError.Context(codingPath:[],debugDescription:"The given data was not valid JSON.",underlyingError:error))}

We’re relying on JSONSerialization to do the heavy lifting. We don’t know if the resulting JSON will match our expected type yet, since we’re getting back a general Any for topLevel inside the do block.

So we have our topLevel, presumably a top-level dictionary or array filled with other NSObject-based instances. How do those make it over to Swift instances?

First we spin up a _JSONDecoder, the private class that conforms to Decoder and contains JSON-specific logic.

Remember T is the generic parameter for the Swift equivalent to topLevel; in this case, it’s [Int] aka Array<Int> so we can decode our array [0, 1, 2].

We’ll construct the final return value by using T’s initializer init(from: Decoder), defined as part of the Decodable protocol.

As with encoding, the flow seems backwards to how my brain works: rather than have the decoder do the work and return an instance of type T, we’re using an initializer on T to construct itself, with the decoder passed along as a parameter.

That’s the high-level view, but there are still a few pieces to dig into: _JSONDecoder, the Decodable protocol, and how our array and integers will get initialized.

JSON Decoder

We instantiated a _JSONDecoder and passed it into the top-level container’s initializer. What’s inside the _JSONDecoder?

The decoder will hold on to the top-level container in self.storage. In our case, this will be an NSArray holding NSNumber objects.

Decoders conform to the Decoder protocol, with the following definition:

/// A type that can decode values from a native format into in-memory representations.publicprotocolDecoder{funccontainer<Key>(keyedBytype:Key.Type)throws->KeyedDecodingContainer<Key>funcunkeyedContainer()throws->UnkeyedDecodingContainerfuncsingleValueContainer()throws->SingleValueDecodingContainer}

If you remember from encoding, the array turned into an unkeyed container, and each item inside the array was a single value container.

We’ll expect the same thing here: the outer array will use unkeyedContainer(), then we’ll loop over each item in the array and call singleValueContainer() for each integer.

Array Reconstruction

Let’s return to the T(from: decoder) initializer, which in this case expands out to Array<Int>(from: decoder). The init(from:) initializer comes from the Decodable protocol, which both Swift arrays and integers conform to.

/// A type that can decode itself from an external representation.publicprotocolDecodable{init(fromdecoder:Decoder)throws}

Finally, we can start building the array:

extensionArray:Decodable/* where Element : Decodable */{publicinit(fromdecoder:Decoder)throws{// Initialize self here so we can get type(of: self).self.init()assertTypeIsDecodable(Element.self,in:type(of:self))letmetaType=(Element.selfas!Decodable.Type)varcontainer=trydecoder.unkeyedContainer()

Some standard initialization stuff, and two important values:

metaType is the type stored in the array, Int in our case. This type has to itself be Decodable.

container is the unkeyed container, an array of anything [Any].

Integer Value Reconstruction

Now that we have our container with the elements to decode and we know the type of the elements, it’s time to loop:

The variable is named subdecoder but comes from a method called superDecoder() — not confusing at all, right? 🤔

Since we could have an array of arrays, or array of dictionaries, or other nested containers, superDecoder() returns a fresh _JSONDecoder instance that wraps the next value in the container.

In our case, that means a _JSONDecoder for a single Int. We decode the integer into element on the second line inside the loop. Remember, metaType is a decodable Int so think of the second line as reading like this, sort of:

letelement=tryInt(from:subdecoder)

Finally, we’ve reached the Int initializer:

// from Codable.swiftextensionInt:Codable{publicinit(fromdecoder:Decoder)throws{self=trydecoder.singleValueContainer().decode(Int.self)}}

Remember, the decoder here is the sub-decoder for just a single value. What’s going on in the decode() call?

// from JSONEncoder.swiftextension_JSONDecoder:SingleValueDecodingContainer{publicfuncdecode(_type:Int.Type)throws->Int{tryexpectNonNull(Int.self)returntryself.unbox(self.storage.topContainer,as:Int.self)!}}

OK, now we’re calling through to an unbox() function. We’re dealing with NSArray and NSNumber instances, so we want to unbox the NSNumber back to a plain Int.

fileprivatefuncunbox(_value:Any,astype:Int.Type)throws->Int?{guard!(valueisNSNull)else{returnnil}guardletnumber=valueas?NSNumberelse{throwDecodingError._typeMismatch(at:self.codingPath,expectation:type,reality:value)}letint=number.intValueguardNSNumber(value:int)==numberelse{throwDecodingError.dataCorrupted(DecodingError.Context(codingPath:self.codingPath,debugDescription:"Parsed JSON number <\(number)> does not fit in \(type)."))}returnint}

First, we have two guard checks to make sure the value isn’t NSNull and that it is indeed an NSNumber.

Next is the code we’ve been waiting for: let int = number.intValue 🎉

Then there’s a final check to make sure we didn’t overflow by turning the integer back to an NSNumber and making sure that value matches what we started with.

Now pop your mental stack all the way back to the array initializer: we were inside a while loop, getting subdecoders, instantiating objects, and then:

The final thing to do is append the integer to self — we’re inside an array initializer here so self is a Swift array.

What’s with the element as! Element cast? The element variable is of type Decodable so we need to cast it to Element (aka Int in our example) before adding it to the array.

The Closing Brace

We made it! All the way from a JSON string, through decodable and decoders, to unkeyed containers, to loops, to individual values.

I like the split in logic here between decodables and decoders:

Decodables only need init(from:) initializers, which are relatively simple.

Decoders contain the logic, state, and storage to do the decoding work.

You get two-way flexibility: you can add your own codable type and have it work with JSON; or, you could add your own encoding format such as XML and as long as you follow the protocol conformance, you can support all codable types.

One price to pay is lots of repeated code in decoders; if you look at the rest of JSONEncoder.swift, you’ll find 18unbox() functions to cover all the integer types, floats, strings, dates, etc.

What would the simplest custom encoder and decoder look like? That’s something I’m curious about and will be trying out next!