杰克逊在自定义 StdDeserializer 中给出 StackOverflowError 读取值

Jackson gives StackOverflowError reading value in custom StdDeserializer

有 2 个简单的 类 像:


@Setter
@Getter
public class Continent {

    private String id;
    private String code;
    private String name;
}

@Setter
@Getter
public class Country {

    private String    id;
    private String    alpha2Code;
    private String    alpha3Code;
    private String    name;
    private Continent continent;
}

阅读以下 yaml 时:

id: brazil
alpha2_code: BR
alpha3_code: BRA
name: Brazil
continent_id: south-america

我想使用 continent_id 从范围为 List<Continent> 的应用程序中检索 Continent

我能想到的最好的事情是使用自定义反序列化器,例如:

public class CountryDeserializer extends StdDeserializer<Country> {

    public CountryDeserializer() {
        super(Country.class);
    }

    @Override
    public Country deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {

        // This works... the `continentId` is retrieved!  
        JsonNode node = jp.getCodec().readTree(jp);
        String continentId = node.get("continent_id").asText();

        // How to access application scoped continents? Use injectable value?
        Continent continent = getContinent(continentId);  
        
        // Read value for other properties; don't want to read other properties manually!
        Country country = jp.getCodec().readValue(jp, Country.class);
        // But unfortunately this throws a Whosebug...
        
        country.setContinent(continent);
        
        return country;
    }
}

但问题是我希望 Jackson 自动读取其他属性。 我不想手动这样做,好像将来添加 属性 它可能会被遗忘,并且对于具有 20 个属性的其他实体,这变得非常麻烦...

我尝试使用 Country country = jp.getCodec().readValue(jp, Country.class); 但是这会导致堆栈溢出异常,因为它显然进入了自定义反序列化器的循环。

有没有办法使用 Jackson 来解决这个问题,或者在这种情况下是否有另一种更好的方法来获取和设置 Continent

请注意,我正在使用一组预定义的域 类 我无法更改。 如果需要,我可以修改对象映射器并添加混合。

我没有使用 CountryDeserializer,而是使用 ContinentReferenceDeserializer 实现了它。 这样,其他 Country 属性将“自动”反序列化。

看起来像:

public class ContinentReferenceDeserializer extends StdDeserializer<Continent> {

    public ContinentReferenceDeserializer() {
        super(Continent.class);
    }

    @Override
    public Continent deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String id = parser.getText(); // returns the continent id (`continent_id` in json)
        Map<String, Continent> continents = (Map<String, Continent>) context.findInjectableValue("continents", null, null);
        return continents.gett(id);
    }
}

它被用在 CountryMixIn 中,例如:

public abstract class CountryMixIn {

    @JsonProperty("continent_id")
    @JsonDeserialize(using = ContinentReferenceDeserializer.class)
    abstract Continent getContinent();
}

请注意,如果您不使用 Mix-ins 而是直接注释 domain/dtoa 类,以上内容也可以应用于这些。

ObjectMapper 可以这样设置:

Map<String, Continent> continents = .. // get the continents

ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Country.class, CountryMixIn.class);
mapper.setInjectableValues(new InjectableValues.Std().addValue("continents", continents));

然后可以这样调用:

String json = .. // get the json
Country country = mapper.readValue(json, Country.class);