开发者

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() {...}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜