Author Archive

Swift 5 Encodable nested and hoisted JSON

November 4, 2019

Ran into an Encodable situation that puzzled me for a bit today.

For those who don’t want to read much, the key observation is that try foo.encode(to: container.superEncoder(forKey: key)) will DELETE any existing key node in the container and the encode foo into a new node with that key. However, nestedContainer(keyedBy: CodingKeys.self, forKey: key) will add encoded items to an existing key if it already exists.

Here’s the blow-by-blow of me figuring that out 🙂

(I have a lot of Swift yet to learn, so it’s likely there is a better way to do all this, but my searching wasn’t finding anything and this works and isn’t entirely horrible, so I thought I’d post it in case it helps someone else).

I have the following model which is already Codable (only Encodable shown for brevity):

public enum UserIdentifier: Encodable {
  case email(String)
  case phone(String)
  case guestID(String)
}

extension UserIdentifier {
  enum CodingKeys: String, CodingKey {
    case address, phone, gid
  }
    
  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case let .email(value):
      try container.encode(value, forKey: CodingKeys.address)
    case let .phone(value): 
      try container.encode(value, forKey: CodingKeys.phone)
    case let .guestID(value): 
      try container.encode(value, forKey: CodingKeys.gid)
    }
  }
}

This then needed to be a field in a struct representing the body of a POST request to an http server:

struct PostBody: Encodable {
  let userID: User.UserIdentifier
  let name: String
  let password: String
}

and what the server expected in the body is JSON of the form:

{
  "address" : "bob@email.com",
  "username" : "Robert Tree",
  "password" : "MyBadPassword!"
}

To see what the output of the default Encodable support will output I created a little test case (I often do this kind of thing in a playground, but this was already embedded in an iOS app so it was easiest to just make a test case):

class Tests: XCTestCase {
  func testCreateUserPostBodyToJSON() throws {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted

    let body = UserService.CreateUserPostBody(userID: User.UserIdentifier.email("bob@email.com"), 
        name: "Robert Tree", 
        password: "MyBadPassword!")

    let jsonData = try encoder.encode(body)
    print(String(data: jsonData, encoding: .utf8)!)
  }
}

and, of course we expect this won’t be right, but it’s our starting point.  Here’s what it outputs:

{
  "userID" : {
     "address" : "bob@email.com"
  },
  "name" : "Robert Tree",
  "password" : "MyBadPassword!"
}

So clearly we need a custom encode function to pull the userID enum contents up to the top level. Let’s try this:

func encode(to encoder: Encoder) throws {
  enum CodingKeys: String, CodingKey {
    case username, password
  }
  var container = encoder.container(keyedBy: CodingKeys.self)
  try container.encode(name, forKey: .username)
  try container.encode(password, forKey: .password)
  try userID.encode(to: encoder)
}

The key is that last line: try userID.encode(to: encoder). Here’s the new output:

{
  "address" : "bob@email.com",
  "username" : "Robert Tree",
  "password" : "MyBadPassword!"
}

Bingo! Just what the doctor ordered!

Except then I try the request to the server and it turns out the api documents for the server are out of date (🤦🏼‍♂️) and what the server really wants is:

{
  "user" : {
    "address" : "bob@email.com",
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Hmmm. Ok, so this is a little more challenging, but it’s what nestedContainer is for, so we’ll try using that:

func encode(to encoder: Encoder) throws {
  enum ParentCodingKeys: String, CodingKey {
    case user
  }
  enum CodingKeys: String, CodingKey {
    case username, password
  }

  var container = encoder.container(keyedBy: ParentCodingKeys.self)
  var nestedContainer = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
  try nestedContainer.encode(name, forKey: .username)
  try nestedContainer.encode(password, forKey: .password)
  try userID.encode(to: container.superEncoder(forKey: .user))
}

Looks fancy. Let’s try it:

{
  "user" : {
    "address" : "bob@email.com"
  }
}

OH NO!!! what happened?!!

Hmmm… That looks like just the output of the last line, so comment out
try userID.encode(to: container.superEncoder(forKey: .user))
and see if the first part is working at least:

{
  "user" : {
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Ok. So the first part is creating things exactly how we want it to, it’s just that last line isn’t adding itself to the already existing "user" : { } node. Rats! Documentation isn’t shedding any light (it *does* in fact shed light, but it’s too subtle for me to catch on a too-quick read; as we’ll see below). Tried this on a whim:

func encode(to encoder: Encoder) throws {
  enum ParentCodingKeys: String, CodingKey {
    case user
  }
  enum CodingKeys: String, CodingKey {
    case username, password
  }

  var container = encoder.container(keyedBy: ParentCodingKeys.self)
  try userID.encode(to: container.superEncoder(forKey: .user))
  var nestedContainer = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
  try nestedContainer.encode(name, forKey: .username)
  try nestedContainer.encode(password, forKey: .password)
}

And the output is:

{
  "user" : {
    "address" : "bob@email.com",
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Success!

So, apparently, superEncoder(forKey: key) (Docs, emphasis mine: “Stores a new nested container for the given key and returns A new encoder instance for encoding super into that container.”) always makes the node for key and overwrites whatever happened to be there before (how rude!). Luckily, nestedContainer(keyedBy: Keys, forKey: key) (Docs: “Stores a keyed encoding container for the given key and returns it.”) doesn’t do that and will just add items to the node if it already exists. Sweet!

Follow up:

It occurs to me that the solution above only works because there’s only a single enum like UserIdentifier to include in the nested structure. They can’t both be first and so the second one would overwrite the first one.

Hopefully there’s another better way to do this that someone will point out in a comment which can handle multiple objects.

Making iOS 12 projects in Xcode 11

September 23, 2019

Some Xcode 11 iOS project templates aren’t iOS 12 compatible out of the box, but it’s relatively easy to make them work, here’s how:

  1. Select File > New… > Project…
  2. Select iOS and “Single View App” template, for example.
  3. Give the app a name, Swift, and be sure to select “Storyboard”, not “SwiftUI” (which needs some View and some requires iOS 13 according to the error message from Xcode), then select the “Next” to create the project.
  4. Select “AppDelegate.swift” and add @available(iOS 13, *) on the line above each of the func definitions in the “UISceneSession Lifecycle”
  5. Add an implementation of the window property inside the AppDelegate class like so: var window: UIWindow?
  6. Open “SceneDelegate.swift” and add @available(iOS 13, *) on the line above class SceneDelegate
  7. Select the project file in the Project Navigator to edit project settings and select the Project in the left pane of the editor
  8. In the “Info” tab, change the “iOS Deployment Target” at the top of the right pane to the appropriate 12.x deployment target for your project.
  9. Build and Run.  Good to go!

I’ve posted the two edited source files in a gist here

And thus he was gone…

August 2, 2019

With a harsh gesture at the ground ahead and loud but guttural roar of uttered spell, there was a bright flash, a sharp and loud “CRACK!”, and a deep groaning and tearing sound as the earth before them ripped open to reveal a vast chasm into which tumbled quantities of rock and debris as the edges collapsed inward. With a long and wrenching curse audible even over the furor of a great host of men trying to calm their startled beasts, the speaker dove forward into the depths of blackness and the earth closed up over him like the jaws of some huge monster closing over it’s prey. As the beasts were brought back under control by their riders, a silence spread over the assembled host with only the lesser grumbles of the earth settling back into place and the cries of the startled birds soaring overhead to be heard.

Memorabilia box reveals another funny item and story…

April 30, 2018

I lived in rural Maui back when there was zero bus service and you had to have a car to get anywhere (though we did moderately well for local area access on bicycles via back roads and pineapple fields).  The fact that it is an island made out of the slopes of two mountains (one of which is 10,000′ tall!) meant a lot of steep rides though, so like any teenager in a rural location in those days, a car was a significant desire. 🙂

My first automobile was a Chevy Love truckthat I purchased when I was 14-1/2 – six months before I could get a permit and license to drive.  $250 and we had to tow it from a neighbor’s yard because it didn’t run.  It was in terrible shape. He’d stopped driving it several years prior because the back of the truck was so rusted out that his dogs’ legs were falling through and the water in the puddles in the unpaved road to our group of houses was splashing up into the cab through the giant rust holes.  Truly an amazing vehicle! 😛  The back tires were extra wide for some reason, but that just made it seem cooler 🙂  It looked something like this, but with a ton of rust holes:

CHEVY-LUV_Truck.jpg

Of course I was stoked to get it and spent the next 6 months getting it running, putting plywood down in the floor of the truck bed, and so on.  Had to get the rust ground off the brakes by a machine shop (raw metal sitting == rust build up fast in Hawaii), clean out the mildew, etc and so on.

The deal with my parents was that if I drove myself and my sister to school (no school buses to the schools we were in) then they’d pay the car insurance and just enough gas money to go those miles.  The truck maintenance and gas for any other trips was on me.

I got my permit at 15, and two weeks later my license (minimum required waiting time).  The muffler was pretty rusted out and so it sounded a bit like a crop duster when I pressed the gas pedal (i.e., loud and frappy).  It also wouldn’t idle so I had to keep tapping the gas pedal at stop lights and stop signs to keep the motor from dying; other teens thought I wanted to race (ha!).

During some periods it wouldn’t start reliably.  Had to push start it pretty regularly so I parked it up an embankment that let me roll it down towards the house and hopefully get it to start (yikes!).  Some periods I’d start it first and then run inside to take my shower before driving to school so that I wouldn’t have to take a second shower after getting all greasy getting it running.

Anyway, at one point I decided to make a stencil out of legal size file folders (only stiff card stock I had access to for free :)) and spray painta logo on the side of the doors to embrace the crappiness of the truck and so I made one that evoked the circular logo of the state that was on the side of government trucks.

I found the stencil in my mementoes box and without further ado or story telling, here it is:

IMG_8357 small for web.JPG

🙂 Apropos, for truly it was a rusted hulk that should have been scrapped, but got me around the island for maybe a year before I upgraded to a $750 car that burned almost a quart of oil a week and a went through about a quart of transmission fluid every two days until I took the transmission out and got it rebuilt, but that ran much more reliably and had much less rust 🙂  (So bad for the environment – we were so clueless back then!  sigh).

15th Birthday Poem from Dad’s grandmother

April 29, 2018

Looking through my memento boxes for things for my dad (Geek’s grandfather)’s memorial service and found this poem my grandmother wrote me for my 15th birthday.  She was a great grandmother.

15th birthday poem from Granny Bee.png

 

It references our relation to Daniel Boone, and the large ranch on the coast of California that our family owned for many generations and that my dad (Boone) grew up on.

Looking back and wishing I’d stuck this regular practice…

April 6, 2018

Looking back at the drawings Geek & I were doing as a daily practice during the summer of 2009 and wishing I’d kept up with it. Imagining how much better I’d be at drawing now if I’d continued to do a drawing a day for the last nearly 9 years (wow! time flies!). That’s roughly 3200 drawings…!

Good reminder that many things take regular, consistent practice.

Resolving to decide which things to fold back into my regular practices and do so.

“Floating in a sea of sadness”

December 17, 2017

 

Floating in a sea of sadness

Looking for an isle of gladness

 

Waves of emotion

A vast ocean

 

No land in sight

Only endless night

 

only sadness

only darkness

 

 

 

Context:  Ten years ago, almost a year after Nina died I often slept in my barn office fighting emotional darkness. Woke up one morning with the first sensations ones of pain – physical and otherwise – again. Curl into a ball under the covers and try to shake it off enough to motivate the day and the words above come to me in bits.

Took me a bit today to realize why I was feeling so adrift and emotionally awash this morning.  And then I looked at the date to check about an upcoming appointment and realized the dates next week and remembered what this time of year means and how our bodies know and remember our grief even if our minds have shied away from remembering.

 

“Help getting into the tech scene in Portland”

September 20, 2017

I get this question a lot, “I’m having trouble getting into the tech industry in Portland, any suggestions?” Usually from recent college graduates or people who are coming into tech from other than a CS degree path.  I just wrote up this response to one and decided I was tired of typing similar stuff in over and over so extractedit and am putting it here:

—-

Jobs resources:

There are more for sure.

While you search (since one can only spend so many hours a day searching), build something that demonstrates your skills and helps you learn more skills. Picking something you see lots of job ads for (if it’s interesting to you) is a decent way to start. Lot of demand for machine learning these days. Taking online courses can help accelerate this learning but won’t get you a job; building something that works and that demonstrates what you can do is much more likely to.

If you find a place you want to work, get to know people there and let them get to know you.(1)  If they like you then they’ll help you know about positions that open up. Learn from them what they need and go teach yourself those things. Ask smart questions (find answers to easy ones online and in documentation).

Finally, big companies like eBay, Apple, Google, Amazon etc open up there internships for current students and recent graduates in (Oct?)/Nov/Dec so inquiring about them can be a good route into larger companies.


1) don’t be creepy! See if you have any friends in common.  See if there’s a meetup or other public social gathering they go to where you can meet them.  Follow on twitter to get to know them over time.  Look for their office having an open office gathering, etc.

“We need more coders…”

June 30, 2017

In his excellent post Brandon Sneed responds to GM CEO’s “We need more coders” comment:  “Let’s talk about the real problem…”

I agree with his comments and would add:

Many large companies seem to waste an astonishing amount of developer productivity. So rather than saying they need more developers, these CEOs might start by fixing their current planning, processes, and methods of working.

Canceled projects/features, mismanaged and/or badly planned projects, developer time wasted in meetings they don’t need to be in or badly run meetings, stupid territorial struggles between upper managers, endless re-organizations, etc. etc.

These kind of things all makes developers less efficient which decreases their job satisfaction and makes them less likely to stick around.

Small companies do much much less of this because they simply can’t afford such waste.

One easy test:

If your company has meetings without clear agendas provided >24hrs in advance of the meeting, and shared post-meeting minutes, then you’re likely wasting developer time. A developer can’t know if they need to attend a meeting if they don’t know what it’s about (agenda) or if they feel like they have to be present to know what’s going on (lack of post-meeting minutes).

Attach an agenda to the meeting invite (>24hrs in advance!) and add only those developers (or anyone else for that matter) who you *must* have in the meeting to the TO/required list. Add anyone else who you want to inform about the meeting and what it’s about to the CC/optional list. Before you send the invite, do any pre-meeting work you can that will enable you to move developers (or anyone else) from the TO/required list to the CC/optional list; use email (don’t interrupt developers!) to ask those two questions you need to ask them before the meeting, for example.

See also @rands’ comment here

Found a file named “Silly Verse for James Dempsey”

April 17, 2016

awash in the miasma
of async blocks on dispatch_queues
bad data flyin’ like kids from pews
(when church lets out and they go free)
and to the console logging spews
apocalyptic, no good news
mood turning into plasma