feat(cmd): accept JSON parameters and parse rich types - #96
Merged
Merged
Conversation
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
force-pushed
the
nyg/issue-89-5557fa
branch
from
September 6, 2026 10:31
6642cbe to
dc1b4f9
Compare
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>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #89.
What
Two layers, kept separate so the existing positional path is unchanged.
Rich type conversion.
MBeanValueParseronly knew primitives, their wrappers,String,BigIntegerandBigDecimal— anything else failed withCannot 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 coversInstant,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.runandsetshare the parser, so both gain this andSetCommandneeded no change.JSON parameters.
rungains-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.Refactor. The three places that derived arity from
parameters.size() - 1are replaced byOperationArguments, a sealed interface over the three parameter sources (positional, JSON array, JSON object), modelled on the existingJmxUrl. 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:Two things worth knowing
Parameter names are
p1,p2, … For a plain standard MBean the JVM does not retain declared parameter names —MBeanOperationInfo.parameterssynthesises"p" + (i + 1)(p0,p1, … for MXBeans). Real names appear only when the MBean supplies them, as Model MBeans and Spring's@ManagedOperationParameterdo. No aliasing was added, becausep1/p2are the declared names and aliasing would shadow real ones;info -o <op>prints what-jexpects.Target-side serial filters. A JVM started with
-Dcom.sun.management.jmxremoteapplies a deserialisation filter to invocation arguments allowing little beyondjava.lang.*andjava.util.*, so passing ajava.timevalue or an enum to such a target fails withfilter status: REJECTED. Verified in a manual smoke test against the shaded jar: the same calls succeed once-Dcom.sun.management.jmxremote.serial.filter.patternis 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-databind3.0.2) rather than 2.x:java.timesupport is built into databind so it is a single declared dependency, andJacksonExceptionis unchecked. The shade plugin needed no change — the existingServicesResourceTransformeralready 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,-jcases inRunCommandTest, rich-type cases inMBeanValueParserTestandSetCommandTest, and 8 new cases inOperationInvocationITagainst the embedded server viaat(Instant)andsum(int[])on the test MBean.One existing assertion changed:
should_throw_when_type_is_unsupportedpinnedparse("x", "java.lang.Object")throwing, which now correctly returns"x". It is replaced by a negative test onjava.lang.Runnableplus tests pinning the newObjectbehaviour.Docs
README (ToC, features, commands table, new "Operation Parameters" section covering both forms, the
p1naming reality, tokeniser quoting and serial filters), the website (nav, new Parameters section, commands table, features grid),docs/dev/architecture.md(new Value Conversion component) anddocs/dev/integration-tests.md(test MBean members and IT counts).🤖 Generated with Claude Code