JSON/ Джексон: Рекурсивная полиморфная десериализация без поля типа

#java #json #jackson

Вопрос:

В настоящее время я борюсь с десериализацией структуры данных JSON, которая выглядит примерно так:

Пример 1:

 {
    "condition": "AND",
    "rules": [
        {
            "id": "FIELD1",
            "field": "FIELD1",
            "type": "string",
            "input": "select",
            "operator": "equal",
            "value": [
                "a1"
            ]
        },
        {
            "id": "FIELD2",
            "field": "FIELD2",
            "type": "string",
            "input": "select",
            "operator": "in",
            "value": [
                "b1"
            ]
        }
    ]
}
 

Пример 2:

 {
    "condition": "AND",
    "rules": [
        {
            "id": "FIELD1",
            "field": "FIELD1",
            "type": "string",
            "input": "select",
            "operator": "equal",
            "value": [
                "a1"
            ]
        },
        {
            "id": "FIELD2",
            "field": "FIELD2",
            "type": "string",
            "input": "select",
            "operator": "in",
            "value": [
                "b1",
                "b2",
                "b3"
            ]
        },
        {
            "id": "FIELD3",
            "field": "FIELD3",
            "type": "string",
            "input": "select",
            "operator": "in",
            "value": [
                "c1",
                "c2",
                "c3"
            ]
        },
        {
            "id": "FIELD4",
            "field": "FIELD4",
            "type": "string",
            "input": "select",
            "operator": "in",
            "value": [
                "d1",
                "d2",
                "d3"
            ]
        },
        {
            "condition": "AND",
            "rules": [
                {
                    "id": "FIELD5",
                    "field": "FIELD5",
                    "type": "string",
                    "input": "select",
                    "operator": "equal",
                    "value": [
                        "e1"
                    ]
                },
                {
                    "id": "FIELD6",
                    "field": "FIELD6",
                    "type": "string",
                    "input": "select",
                    "operator": "in",
                    "value": [
                        "f1",
                        "f1",
                        "f3",
                        "f4",
                        "f5",
                        "f6"
                    ]
                },
                {
                    "condition": "AND",
                    "rules": [
                        {
                            "id": "FIELD7",
                            "field": "FIELD7",
                            "type": "string",
                            "input": "select",
                            "operator": "in",
                            "value": [
                                "g1",
                                "g2",
                                "g3"
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}
 

Есть много примеров такой структуры, которые я должен обработать. Это результат работы построителя правил. Я не могу изменить формат JSON, я должен работать с тем, что у меня есть. Структура рекурсивна, может быть несколько уровней.

Я использую ObjectMapper от Джексона и создаю некоторые внутренние классы для отображения данных.

 static class Wrapper {
        public Condition condition;

        @JsonIgnoreProperties(ignoreUnknown = true)
        @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
        @JsonSubTypes({
            @JsonSubTypes.Type(RuleGroup.class),
            @JsonSubTypes.Type(Rule.class) })
        public List<AbstractRuleObject> rules;
    }

    static abstract class AbstractRuleObject {
        public Condition condition;
        public List<Rule> rules;

        @JsonIgnoreProperties(ignoreUnknown = true)
        @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
        @JsonSubTypes({
            @JsonSubTypes.Type(RuleGroup.class),
            @JsonSubTypes.Type(Rule.class) })
        public List<AbstractRuleObject> ruleGroups;
    }

    static class RuleGroup extends AbstractRuleObject {
        public Condition condition;
        public List<Rule> rules;
    }

    static class Rule extends AbstractRuleObject {
        public String id;
        public String field;
        public String type;
        public String input;
        public Operator operator;
        public List<String> value;
    }
 

Большинство примеров выглядят как Пример 1, и для тех, кто уже работает нормально, но есть более сложные, такие как Пример 2, на самом деле даже более сложные и глубокие, чем пример 2, но структура всегда одна и та же:

Всегда существует «Группа правил» с одним «Условием» и одним списком «Правил», правила могут быть либо «Правилом», либо снова «Группой правил», нет ограничений на то, насколько глубоко вы можете зайти, но я считаю, что это не глубже 4 или 5 уровней. На каждом уровне может быть несколько «групп правил»

Я не могу проанализировать эти более глубокие примеры, с текущим кодом и примером 2 я получаю следующую ошибку:

Не удалось разрешить подтип [простой тип, класс MyClass$AbstractRuleObject]: Не удается вывести уникальный подтип MyClass$AbstractRuleObject (совпадают 2 кандидата)

Ответ №1:

Правило и группа правил не имеют ничего общего, за исключением того, что они оба могут отображаться в списке, поэтому AbstractRuleObject не должны иметь condition и rules в нем-a Rule не имеет этих свойств.

Мы тоже можем избавиться от Wrapper него, так как он идентичен RuleGroup .

Это работает для меня:

 import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;

public class ParseExample {
    static class RuleObject {
    }

    static class RuleGroup extends RuleObject {

        public String condition;
        @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
        @JsonSubTypes({
                @JsonSubTypes.Type(RuleGroup.class),
                @JsonSubTypes.Type(Rule.class)})
        public List<RuleObject> rules;
    }

    static class Rule extends RuleObject{

        public String id;
        public String field;
        public String type;
        public String input;
        public String operator;
        public List<String> value;
    }

    public static void main(String... args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        JsonParser parser = mapper.createParser(ParseExample.class.getClassLoader().getResourceAsStream("test2.json"));
        RuleGroup w = parser.readValueAs(new TypeReference<RuleGroup>() {});
    }
}
 

Версия Джексона:

       <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.12.4</version>
      </dependency>
 

Результат:

 args = {String[0]@1361} []
mapper = {ObjectMapper@1362} 
parser = {UTF8StreamJsonParser@1363} 
w = {ParseExample$RuleGroup@1364} 
 condition = "AND"
 rules = {ArrayList@1366}  size = 5
  0 = {ParseExample$Rule@1368} 
  1 = {ParseExample$Rule@1369} 
  2 = {ParseExample$Rule@1370} 
  3 = {ParseExample$Rule@1371} 
  4 = {ParseExample$RuleGroup@1372} 
   condition = "AND"
   rules = {ArrayList@1380}  size = 3
    0 = {ParseExample$Rule@1382} 
    1 = {ParseExample$Rule@1383} 
    2 = {ParseExample$RuleGroup@1384} 
     condition = "AND"
     rules = {ArrayList@1386}  size = 1
      0 = {ParseExample$Rule@1388}