Skip to content

feat(cmd): accept JSON parameters and parse rich types - #96

Merged
nyg merged 2 commits into
masterfrom
nyg/issue-89-5557fa
Sep 6, 2026
Merged

nyg merged 2 commits into
masterfrom
nyg/issue-89-5557fa

Conversation

@nyg

@nyg nyg commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Closes #89.

What

Two layers, kept separate so the existing positional path is unchanged.

Rich type conversion. MBeanValueParser only knew primitives, their wrappers, String, BigInteger and BigDecimal — anything else failed with Cannot convert "…" to type …, so a large class of operations and writable attributes could not be driven from jmxsh at all. It now falls back to Jackson, dispatching on the shape of the input: a value starting with [ or { is read as a JSON document, everything else as a JSON string. That covers Instant, LocalDate, Duration, UUID, java.util.Date, URI, enums, arrays, collections and maps, with a single deterministic attempt so the reported error is the real one. run and set share the parser, so both gain this and SetCommand needed no change.

JSON parameters. run gains -j/--json, taking every parameter as one JSON value. An array binds by position, an object binds by parameter name — which also disambiguates overloads. The two are mutually exclusive with positional parameters.

> run schedule 2026-01-01T00:00:00Z
> run sum [1, 2, 3]
> run -j '[3, 5]' add
> run -j '{"p1": 3, "p2": 5}' add
> set Deadline 2026-01-01T00:00:00Z

Refactor. The three places that derived arity from parameters.size() - 1 are replaced by OperationArguments, a sealed interface over the three parameter sources (positional, JSON array, JSON object), modelled on the existing JmxUrl. Overload resolution and parameter binding now happen once regardless of where the values came from, and a failed match lists the known signatures instead of claiming the operation does not exist:

> run -j '{"a": 3, "b": 5}' add
Operation add with 2 parameters doesn't exist in bean com.example:type=Calculator, known signatures are add(int p1, int p2)

Two things worth knowing

Parameter names are p1, p2, … For a plain standard MBean the JVM does not retain declared parameter names — MBeanOperationInfo.parameters synthesises "p" + (i + 1) (p0, p1, … for MXBeans). Real names appear only when the MBean supplies them, as Model MBeans and Spring's @ManagedOperationParameter do. No aliasing was added, because p1/p2 are the declared names and aliasing would shadow real ones; info -o <op> prints what -j expects.

Target-side serial filters. A JVM started with -Dcom.sun.management.jmxremote applies a deserialisation filter to invocation arguments allowing little beyond java.lang.* and java.util.*, so passing a java.time value or an enum to such a target fails with filter status: REJECTED. Verified in a manual smoke test against the shaded jar: the same calls succeed once -Dcom.sun.management.jmxremote.serial.filter.pattern is widened on the target. This is a policy of the target JVM, not of jmxsh; the README says so.

Dependency

Jackson 3 (tools.jackson.core:jackson-databind 3.0.2) rather than 2.x: java.time support is built into databind so it is a single declared dependency, and JacksonException is unchecked. The shade plugin needed no change — the existing ServicesResourceTransformer already handles Jackson's service entry.

The uber jar grows from 3.44 MB to 6.03 MB (+2.6 MB, ~73%). That is the real cost of the feature.

Tests

352 unit tests and 78 integration/E2E tests pass. Added OperationArgumentsTest, -j cases in RunCommandTest, rich-type cases in MBeanValueParserTest and SetCommandTest, and 8 new cases in OperationInvocationIT against the embedded server via at(Instant) and sum(int[]) on the test MBean.

One existing assertion changed: should_throw_when_type_is_unsupported pinned parse("x", "java.lang.Object") throwing, which now correctly returns "x". It is replaced by a negative test on java.lang.Runnable plus tests pinning the new Object behaviour.

Docs

README (ToC, features, commands table, new "Operation Parameters" section covering both forms, the p1 naming reality, tokeniser quoting and serial filters), the website (nav, new Parameters section, commands table, features grid), docs/dev/architecture.md (new Value Conversion component) and docs/dev/integration-tests.md (test MBean members and IT counts).

🤖 Generated with Claude Code

Closes #89.

MBeanValueParser only knew primitives, their wrappers, String, BigInteger and BigDecimal, so operations and writable attributes declaring anything else could not be driven from jmxsh at all. It now falls back to Jackson, dispatching on the shape of the input: a value starting with [ or { is read as a JSON document, everything else as a JSON string. That covers java.time types, UUID, java.util.Date, enums, arrays, collections and maps, and it keeps a single deterministic attempt so the reported error is the real one. Because run and set share the parser, both gain this with no change to SetCommand.

RunCommand gains -j/--json, which takes every parameter as one JSON value. An array binds by position; an object binds by parameter name, which also disambiguates overloads. Note that a plain standard MBean does not retain declared parameter names — MBeanOperationInfo synthesises p1, p2, … — so those are the keys users write, and info -o <op> prints them.

The three places that derived arity from the positional parameter count are replaced by OperationArguments, a sealed interface over the three parameter sources. Overload resolution and parameter binding now happen once regardless of where the values came from, and a failed match lists the known signatures instead of claiming the operation does not exist.

Jackson 3 is used rather than 2.x: java.time support is built into databind, so it is a single declared dependency, and JacksonException is unchecked. The uber jar grows from 3.44 MB to 6.03 MB.

Rich types can still be refused by the target JVM, which applies a deserialisation filter to invocation arguments when started with -Dcom.sun.management.jmxremote. That is a policy of the target, not of jmxsh, and the README says how to widen it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nyg
nyg force-pushed the nyg/issue-89-5557fa branch from 6642cbe to dc1b4f9 Compare September 6, 2026 10:31
Six issues reported on the pull request.

The run footer becomes a text block instead of a concatenation (S6126). The
eight success-path and four failure-path invocation tests differed only in the
command and its expected output, so they become two parameterized tests
(S5976); the suite still runs the same fourteen cases. Three assertThatThrownBy
lambdas wrapped two calls that could throw, which hides which one the assertion
is about, so the value construction moves into the Given block (S5778).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Sep 6, 2026

Copy link
Copy Markdown

@nyg
nyg merged commit 0283020 into master Sep 6, 2026
4 checks passed
@nyg
nyg deleted the nyg/issue-89-5557fa branch September 6, 2026 13:37
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.

JSON input for params

1 participant