Jackson Ser/Deser: Proxying an object to/from an id w/ a different key
Apologies in advance. This seems like a simple task, but hours later on Google and with guess/check, I still can't figure it out.
I'm writing a Java convenience wrapper library for an API my company provides. One of the classes looks something like this:
class View extends Model<View>
{
List<Column> columns;
Column primaryColumn;
}
However, our API actually wants a primaryColumnId
integer, not an actual Column
object. I want to maintain the strongly-typed getPrimaryColumn()
and setPrimaryColumn(Column)
in the library to reduce developer error, but I'm having significant difficulty writing some sort of translation between the getter/setter that we need to ser/deser to/from JSON.
I'm using the standard Bean serialization strategy. I'd like to avoid the wholly-custom approach because in reality View
has dozens of fields. Here's what I've figured out so far.
I think (haven't tested yet) that I can handle the serialization case simply by creating a custom JsonSerializer
that looks something like:
public static class ColumnIdSerializer extends JsonSerializer<Column>
{
@Override
public void serialize(Column column, JsonGenerator jsonGenerator,
开发者_运维问答 SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeFieldName("primaryColumnId");
jsonGenerator.writeNumber(column.id);
}
}
And then assigning the annotation to the appropriate place:
@JsonSerialize(using = Column.ColumnIdSerializer.class)
public Column getPrimaryColumn() { /* ... */ }
This allows me to serialize the id
rather than the whole class, and rename the key from primaryColumn
to primaryColumnId
.
Now, we get to deserialization. Here I run into three problems.
The first is that in order to successfully deserialize the column from the id, we have to first have the list of columns. This is solvable using @JsonPropertyOrder
on the class. Great, that's done.
The second is that I need to tell Jackson to look under primaryColumnId
rather than primaryColumn
for the value. I don't know how to do this; the JsonDeserializer
appears to kick in after the key has already been found, so it's too late to modify it. JsonSchema
looks like it might be relevant but I can't find any documentation or internet chatter on how to use it.
The third is that from the custom JsonDeserializer
class I'll have to be able to reference the View
that's being deserialized in order to ask it for a Column
in return for my id
int. There doesn't appear to be a way to do that.
Should I just cave and add a public getPrimaryColumnId()
and setPrimaryColumnId(Integer)
, or is there a way to overcome these obstacles?
So I'd propose something like this:
class CustomView
{
private final View parent;
public CustomView(View view){
parent = view;
}
// Jackson needs a no-arg constructor
public CustomView(){
parent = new View();
}
// ...
public List<Columns> getColumns(){ ... }
public void setColumns(List<Columns> columns){ ... }
public int getPrimaryColumn(){
return parent.getPrimaryColumn().getColumnId();
}
public void setPrimaryColumn(int column){
parent.getPrimaryColumn().setColumnId(column);
}
//...
// don't use `get` in the method name here to avoid serialization
public View rawView(){
return parent;
}
}
If needed this can be written to extend View
, but be careful to mask methods where appropriate.
Turns out that since Jackson does nasty reflection, it can see through private
methods. So, the trick ended up simply being along the lines of:
private void setPrimaryColumnId(Integer id) {...}
private Integer getPrimaryColumnId() {...}
public void setPrimaryColumn(Column column) {...}
@JsonIgnore
public Column getPrimaryColumn() {...}
精彩评论