PUBLIC OBJECT

Your strict naming conventions are a liability

Programmers tend to be pretty obsessive over consistency. Most of that consistency is worthwhile, but some of it is foolish.

For example, let’s take some JSON from GitHub’s gists API:

{
  "files": {
    "Hello.txt": {
      "type": "text/plain",
      "content": "Hello World!\n"
    }
  },
  "created_at": "2014-05-27T02:31:35Z",
  "updated_at": "2015-08-29T14:01:51Z"
}

And we’ll map this JSON structure to some Java classes:

public final class Gist {
  Map<String, GistFile> files;
  Date createdAt;
  Date updatedAt;
}
public final class GistFile {
  String type;
  String content;
}

It’s light work for Gson to decode the document into a Java objects:

Gist gist = gson.fromJson(json, Gist.class);

And moments later, heartbreak! Because createdAt and updatedAt fields are both null! The problem of course is that these field names are camelCase in Java (createdAt) vs. snake_case in JSON (created_at).

Well that sucks. We’ve got three bad choices.

Choice 1: Get the framework to flex its muscle

Gson has built-in magic to convert snakes into camels with its LOWER_CASE_WITH_UNDERSCORES FieldNamingPolicy. We make a global configuration change to our Gson instance and the problem is solved.

Gson gson = new GsonBuilder()
    .setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES)
    .create();

Choice 2: Annotate some stuff

With the @SerializedName annotation, you can tell Gson explicitly what the JSON name is. This mechanism is very powerful because it can completely detach the JSON name (updated_at) from the Java name (mLastUpdatedTimestampLol).

public final class Gist {
  Map<String, GistFile> files;
  @SerializedName("created_at") Date createdAt;
  @SerializedName("updated_at") Date updatedAt;
}

Choice 3: Break Beloved Java Naming Conventions

Nobody is forcing us to name our fields in camel case. We can rebel against the system and just match the naming convention from the JSON:

static class Gist {
  Map<String, GistFile> files;
  Date created_at;
  Date updated_at;
}

Here’s the thing

Though it seems like it’s everyone’s favorite, choice #1 is the wrong choice. The problem is that case mapping adds unnecessary complexity to your program. That case mapping is a subtle friction that slows you down and quietly tries to sabotage your work.

Let’s take the above GitHub Gists example and multiply it by 10,000 to form a typical startup-sized application. You’ve got connections to APIs and datastores and in-flight migrations and deprecations.

One day you’re trying to fix something and that’s led you to deleting a supposedly unused field receivedEventsUrl. Do you remember to search for received_events_url when searching your JavaScript source code?

Maybe you don’t have the JavaScript handy, and so you slack your web team, “Is anyone using a field named receivedEventsUrl ?” and they don’t think to apply the case mapping.

Or maybe your web team is trying to make sense of your bogus code and have been looking for received_events_url everywhere, but can’t figure out where that value comes from.

The fundamental problem is that case mapping breaks search. Though you might enjoy powerful tools and understand their consequences, you aren’t always working in your own codebase. Sometimes you’re in a nearby codebase, and sometimes it’s other people in your codebase.

Plus, even when you do remember, manually editing foo_bar_baz into fooBarBaz is a waste of precious time.

Sadly, case mapping is everywhere. In addition to JSON, you’ll see case mapping anywhere Java programmers are interoperating with other systems. It’s in protocol buffers, Hibernate, and some XML frameworks.

Let’s stop with this nonsense. Either by explicitly declaring the mapping (choice 2) or by avoiding the mapping altogether (choice 3).

PS: Neither Moshi nor Wire do case mapping! Yay!