Steve Grunwell

Open-source contributor, speaker, and electronics tinkerer

A dimly-lit, empty factory building

Factory Methods for Hydrating Objects from JSON

If you’ve worked in a lot of codebases, this scenario will be familiar: somewhere in the app, we’re JSON-decoding a string, then using that to pass arguments to a method (such as a constructor). It may look something like this:

I’d like to humbly ask that you stop doing this. Instead, this post is going to show you how to accomplish the same result with a static, factory method and explain why the latter approach will save you all sorts of headaches.

What’s wrong with JSON-decoding?

JSON is a wonderful format, but it does not offer type-safety, nor can we require that certain keys are present. This isn’t JSON’s fault, of course: its role is to represent data, not ensure that it’s valid.

In our example from earlier, we’re assuming that the JSON object—wherever it might come from—will always have both “first” and “last” keys, but what happens if the data doesn’t match our expectations?

This will trigger an “Undefined property: stdClass::$last” warning, which…isn’t great.

Additionally, if our Person model expects both a first and last name (which is rather presumptuous), we might get a fatal TypeError like “Person::__construct(): Argument #2 ($last) must be of type string, null given”.

What if both keys are present, but the values are not strings? Or maybe we’re given empty strings—this probably isn’t what your application is expecting.

Factory methods for self-validating JSON-decoding

This is an excellent use-case for a so-called “factory method” on our class, which might look something like this:

Now, any time we’re receiving a JSON string like in previous examples, we’ll just pass the string directly into Person::fromJson(): if either the first or last names are missing, we’ll throw a ValueError exception. If we’re given non-string (but non-empty) values, we’re explicitly casting them to strings to satisfy the object constructor.

If we decided to add more fields that may or may not be present (e.g. middle name), we could also use null coalescing to provide reasonable defaults.

The important thing here is that the Person class is now in control of how the JSON data is handled, rather than sticking it in a controller or unrelated method. We use the factory method to build (get it?) the object.

Testing the factory method

As a bonus, it now becomes very easy to test our handling of JSON strings within the factory method(s):

With those tests, we can be sure that any place we’re calling Person::fromJson() we’re handling things as expected.

Factory methods and value objects

If you’ve read my article The Beauty of PHP Value Objects (or attended the talk of the same name at Cascadia PHP 2024), you’re probably thinking “wait, but wouldn’t we be validating things twice if we’re adding factory methods like this to value object classes?”

Winona Ryder from the movie Heathers (1988) with the caption "Oh, you're so smart"

She speaks for all of us.

If your class constructor is already doing the hard work of validating the data, the factory method only has to worry about two things:

  1. Decoding the JSON
  2. Ensuring that all required arguments are present (and in the correct type)

Using our example from earlier, we no longer have to worry about checking that “first” and “last” are not empty, and our factory method can be reduced to this:

In this form, we’re simply ensuring that strings are passed to the constructor arguments, using the null coalesce operator (??) to add reasonable defaults if one or both keys are missing.

Previous

What are Continuous Integration & Delivery (CI/CD)?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Be excellent to each other.