2

I have the following tables Customers, Roles, CustomerRoles. Customers and Roles have a Long PK and CustomerRoles obviously does the many to many mappings. Consider the Roles table to be a fixed table (but not hard coded) defined by the system. I.e. a table driven "enum". I.e. "Admin", "User", etc.

On the Java side, I have a Customer entity and a RoleEntity and I'm using Hibernate / JPA to map.

It's all working how it is now, but I wind up with Json that looks like this:

{
    "customerId": 100000,
    "firstName": "Bob",
    "lastName": "Jenkins",
    "roles": [
      {
        "name": "Admin"
      },
      {
        "name": "Super User"
      }
    ]
  },

What I really want is for it to look like:

"roles": [ "Admin", "Super User" ]

and internally have it FK'ed by the M2M table using the ids. Note the roleid field is set to json ignore, but it still leaves it as a object array rather then a string array.

Obviously, the strings need to be enforced against whats in the Roles table.

Any suggestions?

Customer entity:

@ApiModelProperty(notes="Id of the customer.", required=true, value="100000")
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@JsonProperty(access=Access.READ_ONLY)
private Long customerId = 0L;
@NotNull
@Size(min=2, max=64)
@ApiModelProperty(notes="First name of the customer.", required=true, value="John")
private String firstName;
@NotNull
@Size(min=2, max=64)
@ApiModelProperty(notes="Last name of the customer.", required=true, value="Smith")
private String lastName;
@ManyToMany(cascade={ CascadeType.PERSIST })
@JoinTable(name="CustomerRoles",
           joinColumns={ @JoinColumn(name="CustomerId") },
           inverseJoinColumns={ @JoinColumn(name="RoleId") }
)
private List<Role> roles = new ArrayList<>();

public Long getCustomerId() {
    return this.customerId;
}

public String getFirstName() {
    return this.firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

public String getLastName() {
    return this.lastName;
}

public void setLastName(String lastName) {
    this.lastName = lastName;
}

public List<Role> getRoles() {
    //return new ArrayList<Role>(this.roles);
    return this.roles;
}

Role entity:

@ApiModelProperty(notes="Id of the role.", required=true, value="1")
@Id
//@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long roleId;
@NotNull
@Size(min=2, max=64)
@ApiModelProperty(notes="Name of the role.", required=true, value="Admin")
private String name;

@JsonIgnore
public Long getRoleId() {
    return this.roleId;
}

public String getName() {
    return this.name;
}

public void setName(String name) {
    this.name = name;
}

1 Answer 1

3

Add a new method in your Customer entity

    @JsonProperty("roles")
    public List<String> getRolesAsStrList() {
        return this.roles
            .stream()
            .map(Role::getName)
            .collect(toList());
    }

And then add @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) on roles property

    //other annotations
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private List<Role> roles = new ArrayList<>();

And for POST to automatically bind from String to Role just add two constructor in Role.java

    public Role() {
    }

    public Role(String name) {
        this.name = name;
    }

This will automatically handle payload like this and create two role entity with name "Admin" and "Super User".

{
    "customerId": 100000,
    "firstName": "Bob",
    "lastName": "Jenkins",
    "roles": [ "Admin", "Super User"]
}

However, if you need the id field of role you need to manually handle it before persisting or you can keep name of the role as foreign key in CustomerRoles table assuming name of roles will be unique.

If you need more advanced serialization or deserialization you can write a custom JsonSerializer and JsonDeserializer for that. See JsonComponent if you are using spring boot to serve your api.

4
  • Ya... I was kind of trying to do that for the get side of things. How would it work for the set side? I.e. if somebody posts a string array[] in for a POST request? Would I need to take the array and translate it internally to the List<Role> I assume? I was hoping there was a (as an old boss used to say) magic "bSerializeArrayAsStrings=true" property lol. Commented Nov 3, 2019 at 1:16
  • Extended my answer to handle post requests. Commented Nov 4, 2019 at 11:18
  • Hmm... interesting. Hibernate really is magic lol. I wrote a setRolesAsStringList(), but I'll try this out today. Thanks! Commented Nov 4, 2019 at 14:18
  • @SledgeHammer Thanks, but hibernate actually doesn't do it. It is done by Jakson while deserializing JSON string to object. You can also use custom JsonDeserializer for more advanced magic. If you are using spring boot look at JsonComponent Commented Nov 4, 2019 at 14:57

Not the answer you're looking for? Browse other questions tagged or ask your own question.