4

I have my two models Foo and Bar. Foo has a field barId, therefore has one Bar object associated with it. I can query all my Foo objects and include their assiciated Bar object as so (I am using TypeScript with sequelize-typescript):

Foo.findAll<Foo>({
  include: [{ model: Bar }]
});

Bar object has a JSONB field jsonb_field with structure

{ inner_field1: 'some text', inner_field2: 'some more text' }

I can query Bar objects and filter by inner_field1 as such:

Bar.findAll<Bar>({
  where: { 'jsonb_field': { inner_field1: 'text to find' } }
});

This produces following SQL query:

SELECT ... FROM "Bar" AS "Bar" 
WHERE ("Bar"."jsonb_field"#>>'{inner_field1}') = 'text to find'

So far so good. Now let's try querying Foo objects, include Bar objects and filtering by inner_field1:

Foo.findAll<Foo>({
  where: { '$bar.jsonb_field$': { inner_field1: 'text to find' } },
  include: [{ model: Bar }]
});

Now this throws an exception:

Error: Invalid value [object Object]
    at Object.escape ({project_root}\node_modules\sequelize\lib\sql-string.js:50:11)
    at Object.escape ({project_root}\node_modules\sequelize\lib\dialects\abstract\query-generator.js:917:22)
    at Object.whereItemQuery ({project_root}\node_modules\sequelize\lib\dialects\abstract\query-generator.js:2095:41)
    at _.forOwn ({project_root}\node_modules\sequelize\lib\dialects\abstract\query-generator.js:1937:25)
    ...

For the record, I am including the Bar object correctly, because I can filter by other non-JSONB properties as such:

Foo.findAll<Foo>({
  where: { '$bar.number_field$': 5 },
  include: [{ model: Bar }]
});

As far as I know, the problem lies in Sequelize not being aware of the type of jsonb_field so it throws an error when an object is passed to the where query.

Is there a way around this error, maybe using sequelize.literal() or sequelize.json()?

2 Answers 2

6

Use sequelize.cast and $contains operator:

Foo.findAll<Foo>({
  where: { '$bar.jsonb_field$': {
    $contains: sequelize.cast('{ "inner_field1": "text to find" }', 'jsonb')
  },
  include: [{ model: Bar }]
});

Or do it using sequelize.literal as you suggested:

Foo.findAll<Foo>({
  where: { '$bar.jsonb_field$': {
    $contains: sequelize.literal(`'{ "inner_field1": "text to find" }'::json`)
  },
  include: [{ model: Bar }]
});

Both solutions are vulnerable to SQL Injections, to make sure to escape or remove all characters that could cause problems (",',...)

Yes, I have just answered my own question. You are welcome.

Sign up to request clarification or add additional context in comments.

2 Comments

It will work if you need to check for equality. But in case of [Op.Like], for example, it isn't helpful.
How would you do it if the jsonb_field object has more than just inner_field1, and you'd like to e.g. search for a Foo where Bar.jsonb_field.inner_field2 is equal to xyz?
0

Sequelize has better support for JSON columns nowadays, I think since v6, see https://sequelize.org/docs/v7/querying/json/

For example:

FooModel.findAll({
  where: {
    'jsonb_field.inner_field': 'text to find',
  },
});

Or, when the inner field can be one of several options:

FooModel.findAll({
  where: {
    'jsonb_field.inner_field': { [Op.in]: ['text to find', 'other text to find'] },
  },
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.