Hello,
Any idea regarding my issue?
Regards,
maxxyme
On Friday, April 3, 2020 at 6:20:13 PM UTC+2, maxxyme _ wrote:
>
> OK then...
>
> What I'm trying to achieve is a generic wrapper class like this:
>
> public class Wrapper<T> {
>
> private String id;
> // maybe other common fields...
> private T t;
>
> @JsonCreator(...?)
> public Wrapper(T t) {
> this.t = t;
> }
>
> public String getId() {
> return id;
> }
>
> public void setId(String id) {
> this.id = id;
> }
>
> // other getters & setters for the common fields...
>
> public T get() {
> return t;
> }
>
> }
>
> ... in order to read an external API I have no control over.
>
> e.g. the JSON comes like:
>
> { "id": "3aeb3f363", "foo": { "fooProp1": "fooV1", "fooProp2": "fooV2" } }
>
> to be mapped into a Wrapper<Foo> with Foo :
>
> public class Foo {
>
> private String fooProp1;
> private String fooProp2;
>
> // getters & setters
> }
>
> or similar:
>
> { "id": "3aeb3f363", "bar": { "barProp1": "barV1", "barProp2": "barV2",
> "barProp3": "barV3" } }
>
> to be mapped into a Wrapper<Bar> with Bar :
>
> public class Bar {
>
> private String barProp1;
> private String barProp2;
> private String barProp3;
>
> // getters & setters
> }
>
> ...and so on.
>
> There are numerous "entites" like Foo or Bar (each having their own
> properties & Java class).
>
> Sometimes, you can even have nothing, e.g.:
> { "id": "3aeb3f363" }
>
> which should be mapped to a class of type Wrapper<Void> (the
> java.lang.Void one).
> And in that particular case, the variable t (from Wrapper) would be null;
> in fact I'm trying to mimic the java.util.Optional class which can not be
> extended to add extra properties.
>
> All is done by defining each API call in a Java interface as a method
> signature, OpenFeign build an entire HTTP client and handles the whole
> process of calling Jackson to serialize the parameters in order to send the
> HTTP request then deserialize the HTTP response.
>
> public interface MyAPI {
>
> Wrapper<Foo> getSomeFoo(/* some parameters for the 'Foo' API call */);
>
> Wrapper<Bar> getSomeBar(/* some parameters for the 'Foo' API call */);
>
> Wrapper<Void> doSomething(/* some parameters for another API call */);
>
> }
>
> In a Spring context, the MyAPI is autowired, e.g.
>
> @Autowired
> private MyAPI myAPI;
>
> then you call:
>
> Wrapper<Foo> wrapperFoo = myAPI.getSomeFoo(/* with excepted parameters to
> make the API call */);
>
> And I know this would work because I partly implemented all that, and I
> tried once to debug the call, and at some point before calling the
> constructor public Wrapper(T t), Jackson had the correct Java class for the
> T parameter (e.g. Foo.class)
> The last thing I don't really understand is how to "map" the value of the
> field having the "name" of the "entity" (e.g. "foo" or "bar"), maybe with a
> custom deserializer...?
> That's why I was trying to build a simpler example without the generic
> part in order to understand how might "work" the @JsonCreator annotation.
>
> Hoping I made myself clear?
>
> Regards,
> maxxyme
>
> On Thursday, April 2, 2020 at 7:07:25 PM UTC+2, Tatu Saloranta wrote:
>
>> On Thu, Apr 2, 2020 at 3:05 AM maxxyme _ <[email protected]> wrote:
>> >
>> > OK Tatu, thanks for this start of an explanation.
>> >
>> > So basically what you're saying is the default
>> `JsonCreator.Mode.DEFAULT` does not make the right "guess", despite what
>> says the Javadoc, or maybe I wrongly understood it?
>>
>> Your understanding is correct.
>>
>> > public @interface JsonCreator
>> > {
>> > /**
>> > * Property that is used to indicate how argument(s) is/are bound
>> for creator,
>> > * in cases there may be multiple alternatives. Currently the one
>> case is that
>> > * of a single-argument creator method, for which both so-called
>> "delegating" and
>> > * "property-based" bindings are possible: since
>> > * delegating mode can not be used for multi-argument creators, the
>> only choice
>> > * there is "property-based" mode.
>> > * Check {@link Mode} for more complete explanation of possible
>> choices.
>> > *<p>
>> > * Default value of {@link Mode#DEFAULT} means that caller is to
>> use standard
>> > * heuristics for choosing mode to use.
>> > *
>> > * @since 2.5
>> > */
>> > public Mode mode() default Mode.DEFAULT;
>> >
>> > /**
>> > * Pseudo-mode that indicates that caller is to use default
>> heuristics for
>> > * choosing mode to use. This typically favors use of
>> delegating mode for
>> > * single-argument creators that take structured types.
>> > */
>> > DEFAULT,
>> >
>> > What does that mean "caller is to use default heuristics"?
>>
>> That Jackson will try to guess correct mode, based on existence of:
>>
>> 1. Implicit name for one parameter Creator method has, and
>> 2. There are indications of existing property for POJO (as inferred by
>> getter method)
>>
>> There may be some other logic based on type of parameter (I'd have to
>> dig in code to verify), but these are 2 main pieces.
>>
>> > Am I the caller (or caller code's coder...)? So am I supposed to
>> effectively choose a specific mode and not use the DEFAULT one?
>>
>> Caller here would mean Jackson databind's code that invokes creator
>> method.
>> You as developer should specify mode if heuristics does not work, or,
>> if you just want to ensure right mode is used. I would recommend
>> latter.
>>
>> >
>> > Another thing is that due to how I'm gonna structure my different
>> classes (my example is just a simplified view) I can't/don't want to use
>> @JsonProperty
>> >
>> > If I modify the example given above (in the SO link) as follows:
>> >
>> > public class Wrapper {
>> >
>> > private Inner inner;
>> >
>> > @JsonCreator(mode = Mode.DELEGATING)
>> > public Wrapper(Inner inner) {
>> > this.inner = inner;
>> > }
>> >
>> > public Inner getInner() {
>> > return inner;
>> > }
>> >
>> > }
>> >
>> > public class Inner {
>> >
>> > private String prop;
>> >
>> > @JsonCreator(mode = Mode.DELEGATING)
>> > public Inner(String prop) {
>> > this.prop = prop;
>> > }
>> >
>> > public String getProp() {
>> > return prop;
>> > }
>> >
>> > }
>> >
>> > public class JacksonDeserialization {
>> >
>> > private ObjectMapper om = new ObjectMapper();
>> >
>> > @Test // 1
>> > public void test_deserialization_emptyJson() throws
>> JsonParseException, JsonMappingException, IOException {
>> > Wrapper read = om.readValue("{}", Wrapper.class);
>> > assertThat(read).isNotNull();
>> > assertThat(read.getInner()).isNull();
>> > }
>> >
>> > @Test // 2
>> > public void test_deserialization_innerIsEmpty() throws
>> JsonParseException, JsonMappingException, IOException {
>> > Wrapper read = om.readValue("{\"inner\":{}}",
>> Wrapper.class);
>> > assertThat(read).isNotNull();
>> > assertThat(read.getInner()).isNotNull();
>> > }
>> >
>> > @Test // 3
>> > public void test_deserialization_innerIsSet() throws
>> JsonParseException, JsonMappingException, IOException {
>> > Wrapper read =
>> om.readValue("{\"inner\":{\"prop\":\"42\"}}", Wrapper.class);
>> > assertThat(read).isNotNull();
>> > assertThat(read.getInner()).isNotNull();
>> > assertThat(read.getInner().getProp()).isEqualTo("42");
>> > }
>> >
>> > }
>> >
>> > I get three times the same exception:
>> >
>> > com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
>> construct instance of `Inner` (although at least one Creator exists):
>> cannot deserialize from Object value (no delegate- or property-based
>> Creator)
>> > at [Source: (String)"{}"; line: 1, column: 2]
>>
>> That is right.
>>
>> You are trying to bind a JSON Object into String value (delegation
>> means "bind JSON value, whatever it may be, into value of type I
>> have).
>>
>> If content you get is JSON Object, you would either:
>>
>> 1. Specify "Properties" style: in this case there is no actual
>> property with matching name so `null` would be passed
>> 2. Keep Delegating style, but bind to something that is compatible
>> with JSON Object: for example, Map<String, Object>
>>
>> > com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
>> construct instance of `Inner` (although at least one Creator exists):
>> cannot deserialize from Object value (no delegate- or property-based
>> Creator)
>> > at [Source: (String)"{"inner":{}}"; line: 1, column: 2]
>> >
>> > com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
>> construct instance of `Inner` (although at least one Creator exists):
>> cannot deserialize from Object value (no delegate- or property-based
>> Creator)
>> > at [Source: (String)"{"inner":{"prop":"42"}}"; line: 1, column: 2]
>> >
>> > I still don't get what I'm doing wrong...
>>
>> It seems to me that you are explaining what happens, instead of what
>> you are trying to achieve.
>> It would be easier to help if I knew what you try to do.
>>
>> -+ Tatu +-
>>
>> >
>> > maxxyme
>> >
>> > On Thursday, April 2, 2020 at 2:34:04 AM UTC+2, Tatu Saloranta wrote:
>> >>
>> >> On Wed, Apr 1, 2020 at 5:24 PM maxxyme _ <[email protected]> wrote:
>> >> >
>> >> > Hello,
>> >> >
>> >> > I already posted a question on SO but unfortunately didn't get any
>> relevant answer (just one, and the guy told he's not keen on
>> @JsonCreator...)
>> >> > Everything's detailed there (Java classes & call code):
>> >> >
>> https://stackoverflow.com/questions/60132067/cant-properly-deserialize-json-using-jsoncreator-as-described-in-the-javadoc
>>
>> >> >
>> >> > If someone's willing to point me to what's wrong with my class
>> definitions, esp. with the 2 distinct exceptions:
>> >> > - I don't understand the 1st one (why doesn't it works...?)
>> >> > - For what I understand from the 2nd, Jackson tries to deserialize
>> the whole JSON as for the Inner object... but why?
>> >>
>> >> Because there are 2 different possibilities of what might be expected.
>> >>
>> >> POJO1: expect JSON Object with same properties as POJO:
>> >>
>> >> public class Pojo1 {
>> >> public int x, y;
>> >>
>> >> @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
>> >> public Pojo(@JsonProperty("x") int x0, @JsonProperty("y") int y0) {
>> >> x = x0;
>> >> y = y0;
>> >> }
>> >> }
>> >>
>> >> JSON that can be read: {"x":1, "y":2}
>> >>
>> >> POJO 2: expect JSON _value_ that is used to construct POJO, but is not
>> >> property-based (often not JSON Object, but may be)
>> >>
>> >> public class Pojo2 {
>> >> int x, y;
>> >>
>> >> @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
>> >> public Pojo2(/* Note: NO property name */ int[] args) {
>> >> x = args[0];
>> >> y = args[1];
>> >> }
>> >> }
>> >>
>> >> JSON that can be read: [1, 2]
>> >>
>> >> -------------
>> >>
>> >> So far so good? One thing to note is that "delegating" style is only
>> >> applicable with just one parameter for constructor (or factory
>> >> method); whereas "properties" style works for any number.
>> >>
>> >> But this means that the special case of 1 argument constructor (or
>> >> factory method) can be ambiguous if `mode` is not specified. With
>> >> this:
>> >>
>> >> class NameBean {
>> >> private String name;
>> >>
>> >> @JsonCreator
>> >> public NameBean(String name) {
>> >> this.name = name;
>> >> }
>> >> }
>> >>
>> >> which JSON should this map to/from:
>> >>
>> >> { "name" : "Bob" }
>> >>
>> >> OR
>> >>
>> >> "Bob"
>> >>
>> >> ?
>> >>
>> >> There is no good way to figure out that I am aware of. Heuristics
>> >> fail; and as the answer at SO mentioned, parameter names for
>> >> constructors may or may not be included even on Java 8 (and were never
>> >> included before that; Jackson still only requires Java 7 for
>> >> databind).
>> >>
>> >> This is why you need to either add the `mode` property OR add explicit
>> >> `@JsonProperty` for constructor parameter if you want to force
>> >> Properties-style Creator.
>> >> And if conversely you want delegating style, mode.
>> >>
>> >> I hope this helps,
>> >>
>> >> -+ Tatu +-
>> >
>> > --
>> > You received this message because you are subscribed to the Google
>> Groups "jackson-user" group.
>> > To unsubscribe from this group and stop receiving emails from it, send
>> an email to [email protected].
>> > To view this discussion on the web visit
>> https://groups.google.com/d/msgid/jackson-user/4e07dcf0-74f3-4bcb-b669-5a2f16256d2c%40googlegroups.com.
>>
>>
>>
>
--
You received this message because you are subscribed to the Google Groups
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/jackson-user/95e03043-300c-42f1-a830-c4f49d5a6681%40googlegroups.com.