Field level Custom Annotation for JsonPointer expression

42 views
Skip to first unread message

Aditya Pant

unread,
Sep 18, 2023, 12:39:26 AM9/18/23
to jackson-user
Hello Jackson community,

The input Json have nested fields and different field names. I am looking for a way to deserilize using JsonPointer espression (because it allows path lookup).

I have tried several approaches but none of them worked so far.

- Tried using AnnotationIntrospector = introspector gets invoked which returns a custom deserializer from overridden findDeserializer(). However, custom JsonDeserialiser.deserialize() never invoked on mapper.readValue()

- Tried using ContextualDeserilizer = it gets invoked which returns a custom deserializer. However, custom JsonDeserialiser.deserialize() never invoked on mapper.readValue()

The customer deserialiser only invoked when it is declared on the class (not on field).

Following are classes:
Using jackson with lombok

// custom annotation

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonPointerProperty {

    String value();
}

// Person POJO

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    private String name;
    private Address address;
}

// Address POJO

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Address {

// using custom annotation
    @JsonPointerProperty("/requestModel/streetName")
    private String street;
    private String city;
    private String postcode;
}

// introspector 

public class JsonPointerPropertyIntrospector
        extends NopAnnotationIntrospector {

    @Override
    public Object findDeserializer(Annotated am) {

        final JsonPointerProperty jsonPointerProperty = am.getAnnotation(JsonPointerProperty.class);
        if (jsonPointerProperty != null) {
            return new JsonPointerPropertyDeserializer(
                    jsonPointerProperty.value(),
                    am.getType().getRawClass()
            );
        }

        return super.findDeserializer(am);
    }
}

// custom deserializer
public class JsonPointerPropertyDeserializer
        extends JsonDeserializer<Object> {

    private final String jsonPath;
    private final Class<?> clazz;

    public JsonPointerPropertyDeserializer() {
        this(null, null);
    }

    public JsonPointerPropertyDeserializer(String jsonPath,
                                           Class<?> clazz) {
        this.jsonPath = jsonPath;
        this.clazz = clazz;
    }

    @Override
    public Object deserialize(JsonParser parser, DeserializationContext ctx)
            throws IOException, JacksonException {

        // not reaching here
        JsonNode jsonNode = parser.readValueAs(JsonNode.class);
        JsonNode at = jsonNode.at(jsonPath);

        String value = ctx.readTreeAsValue(jsonNode, String.class);

        System.out.println(value);

        return null;
    }
}


// Unit test

class PersonTest {
    String json;

    @BeforeEach
    void setup() throws IOException {
        json = "{ 'address': { 'requestModel': { 'streetName': 'some street', 'cityName': 'some city' }}}"
                .replace(
                "'",
                "\""
        );
    }

    @Test
    void testPerson() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setAnnotationIntrospector(new JsonPointerPropertyIntrospector());

        final Person person = mapper.readValue(json, Person.class);

        assertEquals("some street", person.getAddress().getStreet(), "Street mismatch");
    }
}

There is some error parsing `requestModel`, but I was hoping it would at least reach to customer deserializer.

Any suggestions or point me what I am doing wrong?

Tatu Saloranta

unread,
Sep 19, 2023, 12:27:49 AM9/19/23
to jackso...@googlegroups.com
On Sun, Sep 17, 2023 at 9:39 PM Aditya Pant <aditya...@gmail.com> wrote:
>
> Hello Jackson community,
>
> The input Json have nested fields and different field names. I am looking for a way to deserilize using JsonPointer espression (because it allows path lookup).
>
> I have tried several approaches but none of them worked so far.
>
> - Tried using AnnotationIntrospector = introspector gets invoked which returns a custom deserializer from overridden findDeserializer(). However, custom JsonDeserialiser.deserialize() never invoked on mapper.readValue()
>
> - Tried using ContextualDeserilizer = it gets invoked which returns a custom deserializer. However, custom JsonDeserialiser.deserialize() never invoked on mapper.readValue()

Those both should work. I didn't see anything obviously wrong in your
code, but there are 2 things that might be problematic: I'll add notes
below.

>
> The customer deserialiser only invoked when it is declared on the class (not on field).
>> Following are classes:
> Using jackson with lombok

This might be problematic -- possibly annotations might not be copied
into generated classes?
Either way, trying things out with regular POJO could rule out Lombok
as being problematic
(Jackson should work just fine with Lombok but there have been some
issues in the past)

>
> // custom annotation
>
> @Target({ElementType.FIELD})
> @Retention(RetentionPolicy.RUNTIME)
> @Documented
> public @interface JsonPointerProperty {
>
> String value();
> }
>
> // Person POJO
>
> @Data


> @NoArgsConstructor
> @AllArgsConstructor
> @JsonIgnoreProperties(ignoreUnknown = true)

^^^ I always recommend removing this annotation when troubleshooting,
it can easily mask problems.
I suspect Lombok might be relevant here; annotations not being copied
into generated classes.

-+ Tatu +-

Aditya Pant

unread,
Sep 19, 2023, 9:12:55 AM9/19/23
to jackson-user
Thank you Tatu for your insights. Appreciate it.
You are right it could be annotations are not copied during annotation processing.
Definitely I will give it a try without lombok annotations.



Aditya Pant

unread,
Sep 19, 2023, 10:19:56 AM9/19/23
to jackson-user

I have removed all lombok annotations from Person and Address classes. Then added getters and setters.

As earlier it reached to JsonPointerPropertyIntrospector and created instance, however it didn't invoke JsonPointerPropertyDeserializer.deserialize(). Similar behavior with ContextualDeserilizer.

Another challenge, in the input json "requestModel" contains mixed data (address, product, person). During deserialization they need to be set on individual member (objects) on a person object. @JsonProperty("requestModel") only works for one field. Adding it to address and product not allowed it seems. "Conflicting getter definitions for property 'requestModel' "

I hope there is a way to combine JsonPath and Jackson behavior

Tatu Saloranta

unread,
Sep 20, 2023, 12:50:30 AM9/20/23
to jackso...@googlegroups.com
On Tue, Sep 19, 2023 at 7:19 AM Aditya Pant <aditya...@gmail.com> wrote:
>
> I have removed all lombok annotations from Person and Address classes. Then added getters and setters.
>
> As earlier it reached to JsonPointerPropertyIntrospector and created instance, however it didn't invoke JsonPointerPropertyDeserializer.deserialize(). Similar behavior with ContextualDeserilizer.

It seems very odd that an instance was created but not used.

Could you file an issue for jackson-databind Github project, including
a minimal reproduction of method not getting called -- it's ok if code
wouldn't do quite what it supposed to as long as it's clear method is
not called.

>
> Another challenge, in the input json "requestModel" contains mixed data (address, product, person). During deserialization they need to be set on individual member (objects) on a person object. @JsonProperty("requestModel") only works for one field. Adding it to address and product not allowed it seems. "Conflicting getter definitions for property 'requestModel' "

I'd need to see full class definition for this to make sure I
understand the issue.

You can add that to the issue filed.

-+ 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 jackson-user...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/95fcf79f-c137-490b-acff-09ed68494b12n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages