杰克逊 + 建造者模式?

111

我希望Jackson可以反序列化一个具有以下构造函数的类:

public Clinic(String name, Address address)

反序列化第一个参数很容易。问题在于Address被定义为:

public class Address {
  private Address(Map<LocationType, String> components)
  ...

  public static class Builder {
    public Builder setCity(String value);
    public Builder setCountry(String value);
    public Address create();
  }
}

以下是构造函数的示例:new Address.Builder().setCity("foo").setCountry("bar").create();

有没有一种方法可以从Jackson中获取键值对以便自己构建Address对象?或者,是否有一种方法让Jackson直接使用Builder类?

6个回答

165
只要您使用的是Jackson 2+,那么现在就有内置支持此功能了。
首先,您需要将此注释添加到您的 Address 类中:
@JsonDeserialize(builder = Address.Builder.class)

然后您需要将此注释添加到您的Builder类中:

接下来,您需要将此注释添加到您的Builder类中:

@JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")

如果您愿意将Builder的create方法重命名为build,将Builder的setter方法的前缀改为with,那么您可以跳过这个第二个注释。

完整示例:

@JsonDeserialize(builder = Address.Builder.class)
public class Address
{
  private Address(Map<LocationType, String> components)
  ...

  @JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")
  public static class Builder
  {
    public Builder setCity(String value);
    public Builder setCountry(String value);
    public Address create();
  }
}

16
如果希望完全摆脱 '@JsonPOJOBuilder' 注解,将 "create" 重命名为 "build" 并在每个构建器 setter 上注释 @JsonProperty - Sam Berry
这是黄金般的。谢谢。 - Mukul Goel
1
现在已经过时了,使用 Lombok 1.18.4,您可以使用 @Jacksonized,它将内部构建器和 jackson 注释替换为单个内容。 - Randakar
2
@Randakar 我认为这并不过时,因为 a) @Jackonized 是 Lombok 中刚发布的实验性功能。我认为没有必要鼓励采用实验性功能。b) 这个问题并没有提及或使用 Lombok。我认为没有必要引入依赖来解决问题。 - Rupert Madden-Abbott
1
好的,我正在更新另一个答案,其中Lombok曾经是相关的,显然我扩大了范围。 - Randakar

25

@Rupert Madden-Abbott的回答是正确的,但是如果你有一个非默认构造函数,例如:

Builder(String city, String country) {...}

然后你应该按照以下方式注释参数:

@JsonCreator
Builder(@JsonProperty("city")    String city, 
        @JsonProperty("country") String country) {...}

14

在这种情况下适合我的解决方法是使用 "Lombok" 的构建器注释。

@Getter
@Builder(builderMethodName = "builder")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    creatorVisibility = JsonAutoDetect.Visibility.ANY
)
我希望这对你也有帮助。

4
从现在开始,使用Lombok 1.18.4版本,您可以使用@Jacksonized注解来代替内部构建器和jackson注解,从而使代码更加简洁。 - Randakar
1
但是如果由于公司规定您无法升级我们的库,这对我来说仍然可以正常工作。 - Davide Castronovo

7
我最终使用 @JsonDeserialize 实现了这个功能,具体如下:
@JsonDeserialize(using = JacksonDeserializer.class)
public class Address
{...}

@JsonCachable
static class JacksonDeserializer extends JsonDeserializer<Address>
{
    @Override
    public Address deserialize(JsonParser parser, DeserializationContext context)
        throws IOException, JsonProcessingException
    {
        JsonToken token = parser.getCurrentToken();
        if (token != JsonToken.START_OBJECT)
        {
            throw new JsonMappingException("Expected START_OBJECT: " + token, parser.getCurrentLocation());
        }
        token = parser.nextToken();
        Builder result = new Builder();
        while (token != JsonToken.END_OBJECT)
        {
            if (token != JsonToken.FIELD_NAME)
            {
                throw new JsonMappingException("Expected FIELD_NAME: " + token, parser.getCurrentLocation());
            }
            LocationType key = LocationType.valueOf(parser.getText());

            token = parser.nextToken();
            if (token != JsonToken.VALUE_STRING)
            {
                throw new JsonMappingException("Expected VALUE_STRING: " + token, parser.getCurrentLocation());
            }
            String value = parser.getText();

            // Our Builder allows passing key-value pairs
            // alongside the normal setter methods.
            result.put(key, value);
            token = parser.nextToken();
        }
        return result.create();
    }
}

这可能是您实现它的方式,但是这个答案实际上并没有回答所提出的问题。@Rupert Madden-Abbott发布的答案应该被标记为被接受的答案。 - kelnos

4
这对我很有帮助:@NoArgsConstructor。 唯一的缺点是可以再次使用 = new ADTO()。 但是,嘿,反正我不喜欢代码警察告诉我如何使用别人的代码 :-) 因此,您可以按照自己的方式使用我的POJO DTOS,无论是使用生成器还是不使用。 我建议:使用Builder,但随便你...
@Data
@Builder
//Dont forget this! Otherwise no Jackson serialisation possible!
@NoArgsConstructor
@AllArgsConstructor
public class ADTO {
.....
}

3

目前还没有支持构建器模式的功能,虽然它已经被要求很长时间了(最终Jira问题http://jira.codehaus.org/browse/JACKSON-469已经提交),如果有足够的需求,可能会在1.8版本中添加该功能(请确保在Jira上投票!)。这是一个合理的附加功能,只是受到开发人员时间的限制。但我认为这将是一个伟大的补充。


2
Codehaus不再提供Jira,但相关问题在此处描述:http://wiki.fasterxml.com/JacksonFeatureBuilderPattern - Paul
2
支持Builder模式的功能早在Jackson 2.2之类的版本中就已经添加了。 - StaxMan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接