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] <javascript:>> 
> 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] <javascript:>. 
> > 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/d8c54f94-7f74-48ce-b5b4-27f0bfeb47f2%40googlegroups.com.

Reply via email to