Skip to content

fix: Add JSON column type for SQL Views#23773

Merged
jason-p-pickering merged 6 commits into
masterfrom
feat-json-column-type-sql-view
Jun 8, 2026
Merged

fix: Add JSON column type for SQL Views#23773
jason-p-pickering merged 6 commits into
masterfrom
feat-json-column-type-sql-view

Conversation

@jason-p-pickering

@jason-p-pickering jason-p-pickering commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Fixes https://dhis2.atlassian.net/browse/DHIS2-21373

Root cause

SqlRowSet.getObject(i) returns a PGobject for PostgreSQL json/jsonb columns. PGobject.toString() returns the JSON text. JacksonRowDataSerializer then calls jgen.writeString() on every grid cell unconditionally, quoting and escaping the already-valid JSON text.

Proposed fix

A RawJsonValue marker record is introduced in dhis-api. HibernateSqlViewStore.populateRows() inspects SqlRowSetMetaData.getColumnTypeName() and wraps non-null json/jsonb values as RawJsonValue. JacksonRowDataSerializer emits RawJsonValue via jgen.writeRawValue()instead of
jgen.writeString().

For CSV/XML outputs, OutputFormatter.maybeFormat() unwraps RawJsonValue to its plain string.

This is a workaround, not a proper fix. See the companion architectural ticket (Ticket 2 below) for why.

Limitations of the approach

  • RawJsonValue leaks a SQL-view-specific concept into dhis-api (JacksonRowDataSerializer, OutputFormatter), both of which are shared by analytics and other Grid consumers.

  • CSV and XML outputs emit the raw JSON string inline, which may not be valid for those formats depending on consumer expectations.

  • Obviously breaks existing clients which may unescape JSON output.

Before:

"rows": [
[
"XX MAL RDT TRK - Age",
"XX MAL RDT - Age",
"",
"",
"{\"icon\": \"star_medium_positive\"}"
],
[
"XX MAL RDT TRK - Village of Residence",
"XX MAL RDT - Village of Residence",
"",
"",
"{\"icon\": \"star_medium_positive\"}"
]
]

After:

[
[
"XX MAL RDT TRK - Age",
"XX MAL RDT - Age",
"",
"",
{
"icon": "star_medium_positive"
}
],
[
"XX MAL RDT TRK - Village of Residence",
"XX MAL RDT - Village of Residence",
"",
"",
{
"icon": "star_medium_positive"
}
],
[
"[DI] ANC Validation rule monitor",
"[DI] ANC Validation rule monitor",
"",
"",
{}
]
]

@jason-p-pickering
jason-p-pickering marked this pull request as draft April 29, 2026 08:29
@jason-p-pickering
jason-p-pickering requested review from a team and netroms April 29, 2026 13:09
@jason-p-pickering jason-p-pickering changed the title feat: Add JSON column type feat: Add JSON column type for SQL Views Apr 30, 2026
@jason-p-pickering jason-p-pickering changed the title feat: Add JSON column type for SQL Views fix: Add JSON column type for SQL Views Apr 30, 2026
@jason-p-pickering
jason-p-pickering marked this pull request as ready for review May 5, 2026 05:55
@jason-p-pickering
jason-p-pickering force-pushed the feat-json-column-type-sql-view branch from fc0a2da to b0b4635 Compare May 5, 2026 06:00
@jbee

jbee commented May 12, 2026

Copy link
Copy Markdown
Contributor

We discussed this in todays platform meeting and we are happy with this approach but it is a breaking change for anyone that has views extracting JSON columns where the response changes from quoted JSON to plain JSON. Before we do that we need the OK from @stian-sandvold

@jason-p-pickering

Copy link
Copy Markdown
Contributor Author

Thanks @jbee . Yes, I think we can maybe include this in the release notes. My feeling is that not many people are using JSON in SQL views, but you never know. Regardless,I think it should be a one line fix on the client side, so even if it does break existing integrations/scripts, it should be a minor fix on the client side. Will wait for @stian-sandvold to weigh in.

@stian-sandvold stian-sandvold left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess my main questions here are:

  1. Why do we need to make this change, when it's already parsable. Is there some specific requirement for this? Since it's potentially breaking clients already expected a parsable to json string.
  2. I saw other custom types mentioned as well, like geometry. Is the plan to add custom handling for those as well?
  3. If we do have a plan to decouple from Grid/analytics, is there any reason to rush this solution in now, instead of working towards a long term solution?

Assuming its valuable to bring this in now, I am for it (Note the other comments); Just want to understand the need and intention :)

for (int i = 1; i <= cols; i++) {
Object value = rs.getObject(i);
grid.addValue(isJson[i] && value != null ? new RawJsonValue(value.toString()) : value);
if (maxLimit > 0 && i > maxLimit) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is a bug that's already present, but caught my attention looking at the code:

i represent the number of columns, while the exception highlights the number of rows.

This is however also the same problem in the old code (addRows).

SqlRowSetMetaData rsmd = rs.getMetaData();
int cols = rsmd.getColumnCount();
boolean[] isJson = new boolean[cols + 1];
for (int i = 1; i <= cols; i++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to keep the code a bit more condense, you could move the content of this for loop into the next, and keep everything in one, instead of spreading the column related code in two blocks. Performance wise, I don't see any advantage of pre-calculating these booleans

* or is null it will return the given parameter object itself
*/
public static Object maybeFormat(final Object object) {
if (object instanceof RawJsonValue json) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from this PR, as far as I can tell in the code, this piece of code is redundant.

Since you're already adding a condition in JacksonRowDataSerializer to not use maybeFormat if the field is an instanceof RawJsonValue, you will in theory never get it in this code execution.

I'd say remove either or, unless there's some reason I cant see we need both checks :)

@jason-p-pickering

Copy link
Copy Markdown
Contributor Author

I guess my main questions here are:

  1. Why do we need to make this change, when it's already parsable. Is there some specific requirement for this? >Since it's potentially breaking clients already expected a parsable to json string.

This came up because I was working on a custom SQL view for an integration script and noticed that the JSON column was not really "JSON". It is as you say, trivial for the client to deal with this, it's just not really that nice.

  1. I saw other custom types mentioned as well, like geometry. Is the plan to add custom handling for those as >well?

No plans for this at the moment with this PR. It could of course be nice to be able to do this. I think we would have to fully decouple Grid first though.

  1. If we do have a plan to decouple from Grid/analytics, is there any reason to rush this solution in now, instead >of working towards a long term solution?

I do not think there is really a rush. Its more that it just is wrong. :-D

Assuming its valuable to bring this in now, I am for it (Note the other comments); Just want to understand the >need and intention :)

Let me know how you want to proceed @stian-sandvold .

@sonarqubecloud

Copy link
Copy Markdown

@stian-sandvold

Copy link
Copy Markdown
Contributor

Let's go ahead with it, but we'll have to share this in release notes since it potentially breaks clients using sql views with jsonb :)

@sonarqubecloud

sonarqubecloud Bot commented Jun 1, 2026

Copy link
Copy Markdown

@jason-p-pickering
jason-p-pickering merged commit ab0aff7 into master Jun 8, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants