Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Duplicate property" issue should mention which class it complains about #1999

Closed
OndraZizka opened this issue Apr 16, 2018 · 8 comments
Closed
Milestone

Comments

@OndraZizka
Copy link

OndraZizka commented Apr 16, 2018

When Jackson 2.9.5 complains about "duplicate property", it does give no reference to the specific class.
If you have hundreds of generated models, it's quite tedious to find out.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Duplicate creator property "id" (index 0 vs 1)
 at [Source: (String)"{
  "description": "patched description"}"; line: 1, column: 1]
        at com.fasterxml.jackson.databind.exc.InvalidDefinitionExc

I assume it happens with other errors too, at least I found in forums: "Could not find creator property with name 'id'" and some more.

Btw, it started with version 2.9.2 - it did not complain before (I assume that was a bug).

Source:
https://github.com/FasterXML/jackson-databind/blob/f42b0dcf247b3abeab77a785548a4e7e6500909b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java

@cowtowncoder
Copy link
Member

cowtowncoder commented Apr 16, 2018

First of all, thank you for reporting this: yes, more context should be provided.

And as to throwing exception: not catching the problem is likely a flaw that was fixed, yes.

It would be great to improve error message here, and I hope to have time to look into this.
One challenge is that sometimes not enough information is included: but I hope this is not the case here. Another smaller concern is that changing exception messages is a (minor) change of observed API (in a way), and can theoretically break existing code if it assumes rigid message to match.

But first things first: checking if information is available; would seem likely it is for class.

@cowtowncoder
Copy link
Member

@OndraZizka I know you mentioned actual code that triggers this somewhere but would it be possible to have sample code here too? I'd like to reproduce the problem in a unit test.

@OndraZizka
Copy link
Author

Sure, for instance, below.
The relevant bits are

    @AllArgsConstructor
    ...
       @JsonIgnore Long id;
       @JsonProperty("id") String roomUid;

Full class:

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer;
import lombok.*;
import lombok.experimental.FieldDefaults;
import org.joda.time.DateTime;

import java.util.List;

@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
        "opened",
        "closed",
        "attendees"
})
public class RoomDto {

    @JsonIgnore
    Long id;
    @JsonProperty("id")
    String roomUid;

    @JsonProperty("opened")
    @JsonSerialize(using = DateTimeSerializer.class)
    DateTime opened;
    @JsonProperty("closed")
    @JsonSerialize(using = DateTimeSerializer.class)
    DateTime closed;
    @JsonProperty("attendees")
    List<LegacyAttendeeDto> attendees;
}

@OndraZizka
Copy link
Author

OndraZizka commented Apr 18, 2018

I think we use default ObjectMapper but if the exception didn't show up, I'll check if there's some special config in effect.

@cowtowncoder
Copy link
Member

@OndraZizka Thank you.

Unfortunately I don't think I can yet reproduce the problem as constructors are missing, along with annotations. Anything related to Lombok has to be expanded as no tests can rely on Lombok (it is one of dependencies I explicitly rule out), so result of its class processing need to be expanded, to the degree they are relevant. In this case I think it may add @ConstructorProperties annotation at least, as well as setters/getters.

But I think I may be able to add implied annotations and constructor.

@OndraZizka
Copy link
Author

OndraZizka commented Apr 19, 2018

Hmm ok. I tried IDEA Lombok plugin's "delombok" but that didn't actually create the all args constructor.

I am not at work now but I will decompile the class which is actually produced.

@cowtowncoder
Copy link
Member

cowtowncoder commented Apr 19, 2018

@OndraZizka thank you.

I was hoping to work on this, but then realized that it really crucially depends on how Lombok decides to annotate the constructor and getters, setters, since exception strongly suggests something odd going on.

@cowtowncoder cowtowncoder added this to the 2.9.6 milestone Apr 19, 2018
@OndraZizka
Copy link
Author

OndraZizka commented Apr 20, 2018

Here is the resulting source (AST).
It is what goes to .class so the Lombok annotations have no further effect (unless processed by something else).

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer;
import java.util.List;
import lombok.AccessLevel;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import org.joda.time.DateTime;

@Accessors(chain = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
        "opened",
        "closed",
        "attendees"
})
public class RoomDto {

    @JsonIgnore
    Long id;
    @JsonProperty("id")
    String roomUid;
    @JsonProperty("opened")
    @JsonSerialize(using = DateTimeSerializer.class)
    DateTime opened;
    @JsonProperty("closed")
    @JsonSerialize(using = DateTimeSerializer.class)
    DateTime closed;
    @JsonProperty("attendees")
    List<LegacyAttendeeDto> attendees;

    @java.beans.ConstructorProperties({"id", "roomUid", "opened", "closed", "attendees"})
    public RoomDto(Long id, String roomUid, DateTime opened, DateTime closed, List<LegacyAttendeeDto> attendees)
    {
        this.id = id;
        this.roomUid = roomUid;
        this.opened = opened;
        this.closed = closed;
        this.attendees = attendees;
    }

    public RoomDto()
    {
    }

    public static RoomDtoBuilder builder()
    {
        return new RoomDtoBuilder();
    }

    public Long getId()
    {
        return this.id;
    }

    public String getRoomUid()
    {
        return this.roomUid;
    }

    public DateTime getOpened()
    {
        return this.opened;
    }

    public DateTime getClosed()
    {
        return this.closed;
    }

    public List<LegacyAttendeeDto> getAttendees()
    {
        return this.attendees;
    }

    public RoomDto setId(Long id)
    {
        this.id = id;
        return this;
    }

    public RoomDto setRoomUid(String roomUid)
    {
        this.roomUid = roomUid;
        return this;
    }

    public RoomDto setOpened(DateTime opened)
    {
        this.opened = opened;
        return this;
    }

    public RoomDto setClosed(DateTime closed)
    {
        this.closed = closed;
        return this;
    }

    public RoomDto setAttendees(List<LegacyAttendeeDto> attendees)
    {
        this.attendees = attendees;
        return this;
    }

    public static class RoomDtoBuilder
    {
        private Long id;
        private String roomUid;
        private DateTime opened;
        private DateTime closed;
        private List<LegacyAttendeeDto> attendees;

        RoomDtoBuilder()
        {
        }

        public RoomDto.RoomDtoBuilder id(Long id)
        {
            this.id = id;
            return this;
        }

        public RoomDto.RoomDtoBuilder roomUid(String roomUid)
        {
            this.roomUid = roomUid;
            return this;
        }

        public RoomDto.RoomDtoBuilder opened(DateTime opened)
        {
            this.opened = opened;
            return this;
        }

        public RoomDto.RoomDtoBuilder closed(DateTime closed)
        {
            this.closed = closed;
            return this;
        }

        public RoomDto.RoomDtoBuilder attendees(List<LegacyAttendeeDto> attendees)
        {
            this.attendees = attendees;
            return this;
        }

        public RoomDto build()
        {
            return new RoomDto(id, roomUid, opened, closed, attendees);
        }

        public String toString()
        {
            return "RoomDto.RoomDtoBuilder(id=" + this.id + ", roomUid=" + this.roomUid + ", opened=" + this.opened + ", closed=" + this.closed + ", attendees=" + this.attendees + ")";
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants