Unfortunately the whole case is complicated enough to make my head ache.
I hope someone else has time to look into it,

-+ Tatu +-

On Wed, Apr 8, 2020 at 7:27 AM maxxyme _ <[email protected]> wrote:
>
> 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.

-- 
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/CAL4a10g5vesMimEc_2eEf5aNiVp54ixwoag2LoVAZ-MnyBGyBA%40mail.gmail.com.

Reply via email to