Skip to content

Draft API for connection and template layers for Redis JSON commands#3327

Draft
Dgramada wants to merge 11 commits intospring-projects:mainfrom
Dgramada:topic/JSON-API-draft-for-connection-and-template-layer
Draft

Draft API for connection and template layers for Redis JSON commands#3327
Dgramada wants to merge 11 commits intospring-projects:mainfrom
Dgramada:topic/JSON-API-draft-for-connection-and-template-layer

Conversation

@Dgramada
Copy link
Copy Markdown
Contributor

Provides a draft version of how the JSON template and connection layer API should look like. The purpose of this PR is to receive feedback and to get a better feeling of what should Spring Data Redis provide for users that would like to use Redis JSON.

  • You have read the Spring Data contribution guidelines.
  • You use the code formatters provided here and have them applied to your changes. Don’t submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.
  • You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 11, 2026
@mp911de mp911de marked this pull request as draft March 12, 2026 14:03
Copy link
Copy Markdown
Contributor

@onobc onobc left a comment

Choose a reason for hiding this comment

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

👋🏻 @Dgramada ,

Thanks for the PR - it is great to see the feature being added. I have left a few comments to address / points to discuss.

* @see <a href="https://redis.io/docs/latest/commands/json.arrindex/">Redis JSON.ARRINDEX</a>
*/
@NullMarked
public final class JsonArrayRange {
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.

We do have a Spring Data Range object org.springframework.data.domain.Range<T>. I wonder if we could that instead?

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.

When you get into the impl PTAL at this Range and see if it something we would want to re-use.

@Dgramada
Copy link
Copy Markdown
Contributor Author

If the interfaces are acceptable, I could close this issue and create a separate one that introduces them alongside their implementations and testing. Would you prefer this approach or would you like me to extend this PR?

@onobc
Copy link
Copy Markdown
Contributor

onobc commented Mar 20, 2026

If the interfaces are acceptable,
I do - they are at a nice simple starting point.

I could close this issue and create a separate one that introduces them alongside their implementations and testing.
Would you prefer this approach or would you like me to extend this PR?

I would just rock this PR (continue on from this point).

Thank you for the quick turnaround - I was on PTO - sorry for the delay.

@Dgramada Dgramada force-pushed the topic/JSON-API-draft-for-connection-and-template-layer branch from 0680f61 to 275ca3e Compare March 24, 2026 12:56
…perations.

Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
@Dgramada Dgramada force-pushed the topic/JSON-API-draft-for-connection-and-template-layer branch from 275ca3e to 2f2f6d3 Compare March 24, 2026 13:02
*/
default List<@Nullable Long> jsonArrIndex(byte[] key, String path, String value, long start) {

Assert.notNull(key, "Key must not be null");
Copy link
Copy Markdown
Contributor

@onobc onobc Mar 24, 2026

Choose a reason for hiding this comment

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

Since the default methods just ultimately chain to the non-default method, this validation will be duplicated because the non-default impl will have to implement the same checks.

Note

This comment applies to the PR

…s. Added connection layer logic for blocking layer. Removed redundant asserts in RedisJsonCommands default methods.

Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
* @since 4.2
*/
@NullUnmarked
public interface JsonOperations<K> {
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.

RedisOperations is set up by having key and value type info (i.e. RedisOperations<K, V>). This menas that value types are always V.

The JsonOperations, in contrast requires type info in its API calls and may deviate from the V at RedisOperations<K, V>.

Imagine RedisOperations<String, Person> and now you would want to store redisOperations.valueOps().set(key, new Order()) because you're doing jsonOps().get(key, Order.class). These concepts don't align.

That being said, the JSON API seems to be able to operate on any types. Me and Mark were chatting about this and will continue the chat tomorrow. Will follow up w/ the outcome here to see if we want to type the JsonOperations w/ K,V or as you have done it w/ K.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My concern with JsonOperations<K, V> would be when used with lets say some data structure like List<E>.
Then we would have something like JsonOperations<String, List> and we would lose the list element type and would have to have to provide it as a parameter in a method overload.

Copy link
Copy Markdown
Contributor

@tishun tishun Mar 25, 2026

Choose a reason for hiding this comment

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

Hey @onobc ,

me and @Dgramada discussed this question and I realized I don't fully understand it. Let me make sure we are on the same page first:

  • you can only call JSON.GET on a key that was saved as a JSON with JSON.SET. If you call set my-key my-value and then json.get my-key you will get (error) Existing key has wrong Redis type.
  • the V generic is relatively ambiguous in this case, as it might have or might not have the same semantics of a typical value one stores in their Redis instance

Overall I had the same kind of conundrum when I was designing the Lettuce APIs : whether the payload is best represented by a simple type (String, byte[]); an complex type (Person.class or V); or an datatype abstraction (JsonObject). IMHO there is no clear and obvious right choice and it all depends on the use case.

What I can say from experience is that the community quickly provided the feedback that they were actually expecting the simplest solution (String) so we had to overload our classes with it.

NB! In Lettuce this also means that we are not able to benefit from early serialization / late deserialization on the client thread and we end up congesting the thread pool that processes IO (since String is final we have no good way to wrap it with this logic).

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.

Thanks for those details @tishun and @Dgramada . We (team) discussed this and what we would like to do is proceed w/ this PR but focus only on the command layer. This can go into main and be available w/ the 4.1 release. The operations aspect of the PR we will move into a branch or gist for safekeeping and exploration - ultimately targeting 4.2 for the operations / API layer in November.

This approach will give us some time to design the API/operations layer while making the commands available in 4.1.

Sound good?

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.

Makes a lot of sense to me personally, @Dgramada what do you think?

…urn type in multiple RedisJsonCommands methods.

Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
…sonCommands overload to RedisConnectionUnitTests. Updated @SInCE to 4.1.

Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
@onobc onobc self-requested a review March 27, 2026 19:43
Copy link
Copy Markdown
Contributor

@onobc onobc left a comment

Choose a reason for hiding this comment

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

👋🏻 @Dgramada I left a couple comments to address. Thanks.

* @param key must not be {@literal null}.
* @param path must not be {@literal null}.
* @param value must not be {@literal null}. {@literal null} values should be represented as JSON "null" values.
* @param start index to start searching from.
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.

May be helpful to add inclusive for start and exclusive for stop.

*
* @param key must not be {@literal null}.
* @param path must not be {@literal null}.
* @param start index to start trimming from.
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.


@Test // GH-3327
@EnabledOnCommand("JSON.ARRAPPEND")
void jsoNArrAppend() {
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 noticed the tests are not running because there is no "JSON.*" command returned from the conditions on the server (i.e. the tests never run).
I believe we need to guard this on version @EnabledOnRedisVersion(8.2) (is 8.2 correct version?).

When they do run, you will find that they run into a StackOverflow due to the DefaultedRedisConnection not implementing the new methods by invoking the delegate. For example, this fixes the recursion on the jsonSet:

	@Override
	public Boolean jsonSet(byte[] key, String value) {
		return this.delegate.jsonSet(key, value);
	}

	@Override
	public Boolean jsonSet(byte[] key, String path, String value, JsonSetOption option) {
		return this.delegate.jsonSet(key, path, value, option);
	}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The current testing setup that downloads Redis from source shouldn't have the JSON commands. Redis JSON is a separate module that was included in Redis 8.0.0.

However the current Makefile setup doesn't turn on the necessary flag to include the modules like JSON, Search, Bloom Filter, etc. Furthermore if we decide to test JSON through source this would require adding additional dependencies for the JSON module to work, which is not ideal. You can find the module flag and all the necessary dependencies for everything to work here.

I have an ongoing pull request that migrates the current Makefile setup to use Docker containers instead of downloading Redis from source. Images of Redis 8.0.0 and onwards have all the Redis modules baked in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I would like to mention that this PR is missing the Jedis implementation because it depends on #3315.

Also there is currently 1 test failing which is the jsonArrAppend() test. This might be a bug on the Lettuce side, but I must verify this. If the problem is in Lettuce, we could drop this method and introduce it in the future once it is fixed.

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 would like to mention that this PR is missing the Jedis implementation because it depends on #3315.

Looks like @mp911de just merged that a few hours ago.

Also there is currently 1 test failing which is the jsonArrAppend() test. This might be a bug on the Lettuce side, but I must verify this. If the problem is in Lettuce, we could drop this method and introduce it in the future once it is fixed.

Good plan - lmk what you find.

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.

The current testing setup that downloads Redis from source shouldn't have the JSON commands. Redis JSON is a separate module that was included in Redis 8.0.0.

However the current Makefile setup doesn't turn on the necessary flag to include the modules like JSON, Search, Bloom Filter, etc. Furthermore if we decide to test JSON through source this would require adding additional dependencies for the JSON module to work, which is not ideal. You can find the module flag and all the necessary dependencies for everything to work here.

I have an ongoing pull request that migrates the current Makefile setup to use Docker containers instead of downloading Redis from source. Images of Redis 8.0.0 and onwards have all the Redis modules baked in.

Ahh ok, this makes sense now. Hopefully the docker-compose PR gets approved and merged.

Copy link
Copy Markdown
Contributor Author

@Dgramada Dgramada Apr 1, 2026

Choose a reason for hiding this comment

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

Hey @onobc,
A quick update from me. jsonArrAppend() is broken in Lettuce so I will drop the method from the PR.

Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
…mmands.

Signed-off-by: Yordan Tsintsov <yordan.tsintsov@redis.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants