Making an existing API field multi-value

The evolution of APIs is inevitable. One of the changes that can be required is that a field of your API that used to be a single value needs to support multiple values.

Let's take an example of a Person JSON object containing a sub-object with the details of the person's identity document:

    {
      "name": "Max MusterMann",
      "dateOfBirth": "1970-01-01",
      "identityDocument": {
        "countryOfIssue": "DE",
        "type": "PASSPORT",
        "documentNumber": "999999999"
      }
    }
    

Ideally, we would change the "identityDocument" field's type from a JSON object to an array:

    {
      "name": "Max MusterMann",
      "dateOfBirth": "1970-01-01",
      "identityDocuments": [
        {
          "countryOfIssue": "DE",
          "type": "PASSPORT",
          "documentNumber": "999999999"
        },
        {
          "countryOfIssue": "DE",
          "type": "NATIONAL_ID",
          "documentNumber": "888888888"
        }
      ]
    }
    

But it is common that there will have to be a transition period when some existing clients will not support this change yet.

For requests sent from the clients to our API we can use a nice feature of the Jackson deserializer. Example Java code:

    record Person(
        String name,
        LocalDate dateOfBirth,
        @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
        List<IdentityDocument> identityDocument) {}
    

If a client sends a single object instead of the array, the value will be accepted and converted automatically.

For responses sent to the client we have to be more careful though. Although Jackson has a symmetrical feature WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, it only works if the POJO field is a collection containing exactly one element. In case our list of identity documents is empty (e.g. created by null identityDocument field in the old version of the API) or contains more than one element (created by some client that already uses the new version of the API), the deserialized value will be a JSON array - something the old client will not like.

So if our extended value is inside a data object that our API sends out, we probably have to add another field and make sure the old field still returns an object (or null). We will probably need to add a mapping logic handling the multiple elements case, e.g. by sending the first list element in the old non-array field:

    {
      "name": "Max MusterMann",
      "dateOfBirth": "1970-01-01",
      "identityDocuments": [
        {
          "countryOfIssue": "DE",
          "type": "PASSPORT",
          "documentNumber": "999999999"
        },
        {
          "countryOfIssue": "DE",
          "type": "NATIONAL_ID",
          "documentNumber": "888888888"
        }
      ],
      "identityDocument": {
        "countryOfIssue": "DE",
        "type": "PASSPORT",
        "documentNumber": "999999999"
      }
    }
    
You can comment on this post on LinkedIn.

Back to all blog posts