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.
