Heading image for post: Dealing with nested JSON objects in PostgreSQL

PostgreSQL

Dealing with nested JSON objects in PostgreSQL

Profile picture of Craig Hafer

Over the past few weeks I've been having to deal with nested JSON columns in Postgres. It was turning into me just throwing queries at it until something stuck. Well, as it would turn out, it's actually very simple! I just needed to take the time to sit down and learn about a few operators and what they're actually doing.

With that being said, here is a condensed version of some things I learned that helped me navigate through JSON columns in Postgres.

First, let's create a very basic table containing a json column

create table sample_table (json_data jsonb);

Now, let's seed this table with some data about a few different vehicles

insert into sample_table
values 
    ('{ "year": "2011", "make":"Toyota", "model":"Camry", "misc": {"color": "Gray", "doors": "4"}}'),
    ('{ "year": "2017", "make":"Honda", "model":"Civic", "misc": {"color": "White", "doors": "4"}}'),
    ('{ "year": "2017", "make":"Toyota", "model":"Camry", "misc": {"color": "Red", "doors": "2"}}'),
    ('{ "year": "2023", "make":"Honda", "model":"Accord"}'),
    ('{ "year": "1908", "make":"Ford", "model":"T", "misc": {"doors": "2"}}')
;

Now that we have our data, let's go over some of the basics.

If you've read my post about the Bacon Cannon, then you know that I am a firm believer that unique operators deserve unique names. So, as I cover some unique operators, I will be throwing in their nicknames, at no extra charge to you!

The -> Operator

Using the -> (Stabby) operator will return JSON data. So here we can see a few examples of how we would get the json values out of the column:

select json_data -> 'make' from sample_table;
select json_data -> 'model' from sample_table;
select json_data -> 'year' from sample_table;
select json_data -> 'misc' from sample_table;

image

As you can see, 'misc' is a nested JSON object. If you want to go another layer deeper into a nested JSON object, we can simply add another ->

select json_data -> 'misc' -> 'color' as color from sample_table;

image

The ->> Operator

Now, as I said before, this isn't giving us text values. These quotations are an indicator that ->is returning JSON. In order to grab the text value we need to use the ->> (Double Stabby) operator. This is equivalent to adding ::text afterwards, typecasting it to a text.

select json_data -> 'misc' ->> 'color' as color from sample_table;

image

The #> and #>> operators

A simpler way to do nested JSON attributes is to use the #> (Waffle Cone) operator with an array of the path you want to follow.

select json_data #> Array['misc','color'] as color from sample_table;

Just like before, a single > returns JSON, so if we wanted to grab the actual text value, we'll need to use the #>> (Double Waffle Cone) operator.

select json_data #>> Array['misc','color'] as color from sample_table;

The ?, ?|, and ?& operator

Okay, but what if you want the entire row that matches a certain criteria? We'll move this into a where clause, and then use the ? operator.

A good way to think of this operator is to replace it with the word "contains".

For example the next query would read in english as

"select all from sample_table where json_data's misc doors contains the value 4"

This query will return to us every row where there is a 4 door car

select * from sample_table
where json_data #> Array['misc', 'doors'] ? '4'
;

image

This query will return to us every row containing a Toyota or a Honda

select * from sample_table
where json_data -> 'make' ?| Array['Toyota','Honda']
;

image

This query will return to us every row containing both color and doors keys inside of misc.

select * from sample_table
where json_data -> 'misc' ?& Array['color', 'doors']
;

image

(Notice this doesn't contain the row that has just {"misc": {"doors": "2"}}

The @> and <@ operators

If you are looking for a specific JSON object inside of another, you want to use the @> *(Penguin)*operator.

For example, if were looking specifically for {"key": "value"}:

select * from sample_table
where json_data @> '{"make": "Toyota"}'
;

As you might have guessed, we can look for nested JSON objects

This will return to us every row containing red, 2-door vehicles"

select * from sample_table
where json_data @> '{"misc": {"color":"Red", "doors":"2"}}'
;

There is also the <@ (Ice Cream Cone or Reverse Penguin) operator that behaves the same, only in reverse.

select * from sample_table
where '{"misc": {"color":"Red", "doors":"2"}}' <@ json_data
;

In conclusion

I hope this helps clear up any confusion you might have had about querying nested JSON objects in Postgres.

If you want to learn more about these operators or if you want to see what else you can do, here is a link to the docs for JSON functions in Postgres.


Photo by: Karen Ciocca

More posts about PostgreSQL

  • Adobe logo
  • Barnes and noble logo
  • Aetna logo
  • Vanderbilt university logo
  • Ericsson logo

We're proud to have launched hundreds of products for clients such as LensRentals.com, Engine Yard, Verisign, ParkWhiz, and Regions Bank, to name a few.

Let's talk about your project