#java #json #jackson #jackson-databind #jackson2
Вопрос:
Я использую Джексона для десериализации JSON. Входной JSON может быть 2 типов CustomerDocument
или Single Customer
. CustomerDocument
будет иметь а CustomerList
, которое может состоять из огромного количества Customer
, и то Single Customer
будет иметь только а Single Customer
. Следовательно, Джексон должен справиться с 2 вещами:
- Определите , является ли предоставленный JSON a
CustomerDocument
, если да, то десериализуйте элементы поCustomerList
одному, а не сохраняйте всеList
целиком, чтобы уменьшить использование памяти. - Определите, является ли предоставленный JSON a
single Customer
, и если да, то десериализуйтеcustomer
его напрямую.
Я в состоянии добиться этого, и все работает так, как ожидалось, но когда я предоставляю CustomerDocument
, он не может прочитать @Context
пару ключ-значение, поскольку она уже была прочитана во время проверки на наличие одного Customer
(как вы упомянули в пункте 2). Я думаю, что приведенный ниже код прояснит проблемы:
Ниже приведен JSON, который я пытаюсь десериализовать:
{
"@context": [
"https://stackoverflow.com",
{
"example": "https://example.com"
}
],
"isA": "CustomerDocument",
"customerList": [
{
"isA": "Customer",
"name": "Batman",
"age": "2008"
}
]
}
Ниже приведен мой класс POJO для клиентов:
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer implements BaseResponse {
private String isA;
private String name;
private String age;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonSubTypes({
@JsonSubTypes.Type(value = Customer.class, name = "Customer")})
interface BaseResponse {
}
Ниже приведен мой Main
класс, который прочитает JSON InputStream
и примет решение о том, является ли предоставленный входной JSON CustomerList
или Single Customer
, а затем десериализуется соответствующим образом:
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
try {
BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
System.out.println("SINGLE EVENT INPUT" baseResponse.toString());
} catch (Exception e) {
System.out.println("LIST OF CUSTOMER INPUT");
//Go until the customerList has been reached
while (!jsonParser.getText().equals("customerList")) {
System.out.println("Current Token Name : " jsonParser.getCurrentName());
if (jsonParser.getCurrentName() != null amp;amp; jsonParser.getCurrentName().equalsIgnoreCase("@context")) {
System.out.println("WITHIN CONTEXT");
}
jsonParser.nextToken();
}
jsonParser.nextToken();
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, BaseResponse.class);
System.out.println(event.toString());
}
}
}
}
Following is the output I am getting:
LIST OF CUSTOMER INPUT
Current Token Name : isA
Customer(isA=Customer, name=Batman, age=2008)
Как мы видим, это печать, только Current Token Name: isA
я ожидал бы, что она будет напечатана isA
, и @Context
потому что она присутствует перед isA
. Но я знаю, что он не печатается, потому что он уже прошел это из-за следующей строки кода в try
блоке:
BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
Ниже Single Customer
приведен JSON (только для справки, и это работает нормально):
{
"@context": [
"https://stackoverflow.com",
{
"example": "https://example.com"
}
],
"isA": "Customer",
"name": "Batman",
"age": "2008"
}
Может ли кто-нибудь, пожалуйста, подсказать мне, как я могу этого достичь, и есть ли лучшее решение этой проблемы?
Пожалуйста, обратите внимание:
CustomerList
Может быть многоCustomers
, поэтому я не хочу хранить всеCustomerList
в некоторыхList
, так как это может занять много воспоминаний. Следовательно, я используюJsonParser
, чтобы я мог читать по одномуJsonToken
за раз.- Кроме того, я не хочу создавать
CustomerList
класс вместо того, чтобы читать его по одномуCustomer
за раз и десериализовывать. - Структура JSON не может быть изменена, так как она исходит из другого приложения, и это стандартный формат для моего приложения.
Ответ №1:
Я исправил код, я удалил блок catch. CUSTOMERS_LIST
свойство различается между одним и несколькими клиентами.
JacksonMain.java
public class JacksonMain {
private static final String CUSTOMERS_LIST = "customerList";
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customers.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
TreeNode treeNode = objectMapper.readTree(jsonParser);
if (Objects.isNull(treeNode.get(CUSTOMERS_LIST))) {
BaseResponse baseResponse = objectMapper.treeToValue(treeNode, BaseResponse.class);
System.out.println("SINGLE EVENT INPUT: " baseResponse);
} else {
System.out.println("LIST OF CUSTOMER INPUT");
ArrayNode customerList = (ArrayNode) treeNode.get(CUSTOMERS_LIST);
for (JsonNode node : customerList) {
BaseResponse event = objectMapper.treeToValue(node, BaseResponse.class);
System.out.println(event.toString());
}
}
}
}
Я создал два файла json ресурсов
Клиент.json
{
"@context": [
"https://stackoverflow.com",
{
"example": "https://example.com"
}
],
"isA": "Customer",
"name": "Batman",
"age": "2008"
}
Клиенты.json
{
"@context": [
"https://stackoverflow.com",
{
"example": "https://example.com"
}
],
"isA": "CustomerDocument",
"customerList": [
{
"isA": "Customer",
"name": "Batman",
"age": "2008"
}
]
}
CustomerDocument.java
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
@ToString
public class CustomerDocument implements BaseResponse {
@JsonProperty
private String isA;
@JsonProperty(value="name")
private String name;
@JsonProperty(value="age")
private String age;
}
Customer.java
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer implements BaseResponse {
private String isA;
private String name;
private String age;
}
BaseResponse.java
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonSubTypes({
@JsonSubTypes.Type(value = Customer.class, name = "Customer"),
@JsonSubTypes.Type(value = CustomerDocument.class, name = "CustomerDocument")})
@JsonIgnoreProperties(ignoreUnknown = true)
interface BaseResponse {
}
В интерфейсе я добавил аннотацию JsonIgnoreProperties и добавил еще один пропущенный тип JsonSubType. [Пользовательский документ]
Оба дела сейчас работают.
Тесты производительности:
Size: 1,065,065 records / customers handled
Time in seconds: 30.208
Комментарии:
1. Спасибо вам за ваш ответ. Я в состоянии понять, как вы этого добиваетесь. Но у меня есть одно сомнение, что ранее я использовал следующую строку кода
final JsonNode customerNode = jsonParser.readValueAsTree();
для чтения по одному клиенту за раз изCustomerList
. Это гарантировало мне, что у меня всегда будет только по одномуCustomer
в памяти за раз, но в коде, который вы указали, вы храните полныйCustomerList
кодArrayNode customerList
. Разве этот магазин не был бы целымCustomerList
? ИхCustomerList
может быть огромное количествоCustomers
, поэтому это может повлиять на производительность.2. Я не эксперт по Java или Джексону, поэтому хочу получить подтверждение этого. Есть ли какой-нибудь способ избежать такого полного хранения
CustomerList
? Поскольку это также входит в мои требования, во время выполнения я должен иметь в памяти только по одномуCustomer
за раз. Это требование отчасти противоречит моему другому требованию (разграничение ч/бCustomerDocument
иSingle Customer
). Пожалуйста, помогите мне в этом и предоставьте некоторые обходные пути или предложения, которые можно использовать для работы с этим кодом в соответствии с требованиями. Заранее большое спасибо и с нетерпением ждем ваших предложений.3. Сколько записей должно обрабатываться в одном запросе?
4. Спасибо за ваш ответ и за то, что помогли мне с этим вопросом. Если есть
CustomerList
, то в нем может быть 1000 или 10000sCustomer
, поэтому я хочу убедиться, что в данный момент в памяти есть только одинCustomer
. Это означает, что я хотел бы читать по одномуCustomer
за раз и переходить к следующему, а не хранить полноеCustomerList
в памяти, а затем просматривать его. В коде, который я указал в вопросе, я делаю что-то подобное, но проблема в том, что я упускаю информацию, содержащуюся в@context
нем, поэтому сейчас пытаюсь достичь и того, и другого. Любые предложения
Ответ №2:
Наконец, я смог заставить его работать, Вместо того, чтобы читать блок @context
in catch
, я попытался прочитать его до try-catch
.
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
//Go until the customerList has been reached
while (!jsonParser.getText().equals("isA")) {
System.out.println("Current Token Name : " jsonParser.getCurrentName());
if (jsonParser.getCurrentName() != null amp;amp; jsonParser.getCurrentName().equalsIgnoreCase("@context")) {
System.out.println("WITHIN CONTEXT");
}
jsonParser.nextToken();
}
try {
BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
System.out.println("SINGLE EVENT INPUT" baseResponse.toString());
} catch (Exception e) {
System.out.println("LIST OF CUSTOMER INPUT");
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, BaseResponse.class);
System.out.println(event.toString());
}
}
}
}