Джексон Парс JSON Object как массив объектов - PullRequest
0 голосов
/ 07 мая 2018

Я пытаюсь проанализировать следующий JSON в Джексоне:

{
  "x:y" : 1,
  "x:z" : 2,
  "u:v" : 3,
  // Several dynamically generated entries...
}

Данные отформатированы таким образом и вне моего контроля. Записи несколько динамичны, но всегда имеют вид:

"first:second" : value

Я пытался сериализовать это в контейнерный класс:

private static class MyClass {
    String first;
    String second;
    Number value;

    @JsonCreator
    public MyClass(@JsonProperty("both") String both, @JsonProperty("value") Number value) {
        String[] split = both.split(":");
        first = split[0];
        second = split[1];
        this.value = value;
    }
}

Но я получаю сообщение об ошибке:

Исключение в потоке "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Невозможно десериализовать экземпляр entry.JacksonObjectTest $ MyClass [] из маркера START_OBJECT

Имеет смысл для меня; Я пытаюсь разобрать каждое поле объекта JSON в массив объектов, и Джексон, очевидно, не слишком рад этому. Пренебрежение @JsonProperty("both") дает:

Исключение в потоке "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Неверное определение типа для типа entry.JacksonObjectTest $ MyClass: Аргумент # 0 не имеет имени свойства, не является Injectable: не может использоваться как Creator [ конструктор для entry.JacksonObjectTest $ MyClass, аннотации: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator (mode = DEFAULT)}]

Что также имеет смысл для меня; он не имеет ни малейшего понятия, как анализировать этот конструктор (что на самом деле та же проблема, что и выше; я помещаю аннотацию просто маскируя эту ошибку другим).

Итак, мой вопрос: как заставить Джексона понять, чего я хочу здесь?

MCVE

public class JacksonObjectTest {
    public static void main(String[] args) throws IOException {
        String data = "{\"x:y\":1,\"x:z\":2,\"u:v\":3}";
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(data);
        MyClass[] out = mapper.readValue(node.traverse(), MyClass[].class);
        System.out.println(out);
    }

    private static class MyClass {
        String first;
        String second;
        Number value;

        @JsonCreator
        public MyClass(@JsonProperty("both") String both, @JsonProperty("value") Number value) {
            String[] split = both.split(":");
            first = split[0];
            second = split[1];
            this.value = value;
        }
    }
}

EDIT : Как уже упоминалось в комментариях, я знаю о способе использования TypeReference<Map<String,Number>>. Это работает, но я пытался сделать мой синтаксический код как можно более обобщенным и универсальным, и использование этого решения означает, что мне нужно выполнить дополнительное преобразование после анализа, чтобы получить MyClass[] (сначала анализ для Map<String,Number>, затем обработка что за MyClass[]). Есть ли способ пропустить посредника (IE: скажите Джексону, как обработать блоб JSON известного форматирования в тип данных)?

Ответы [ 3 ]

0 голосов
/ 07 мая 2018

РЕДАКТИРОВАТЬ : Другие предоставленные ответы лучше отвечают на вопрос в целом. Оставим это на месте, чтобы предоставить ссылку на решение, упомянутое в комментариях (вероятно, проще для тех, кто просто хочет быстро отформатировать данные).


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

Используемый подход, как упомянуто в комментариях: сначала проанализировать как Map<String,Number>, а затем преобразовать его в List<MyClass>:

public class JacksonObjectTest {
    public static void main(String[] args) throws IOException {
        String data = "{\"x:y\":1,\"x:z\":2,\"u:v\":3}";
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(data);
        // Note the difference in these two lines from the MCVE.
        Map<String,Number> interim = mapper.readValue(node.traverse(), new TypeReference<Map<String,Number>>(){});
        List<MyClass> out = interim.entrySet().stream().map(MyClass::new).collect(Collectors.toList());
        System.out.println(out);
    }

    private static class MyClass {
        String first;
        String second;
        Number value;

        public MyClass(Entry<String, Number> entry) {
            String[] split = entry.getKey().split(":");
            first = split[0];
            second = split[1];
            value = entry.getValue();
        }
    }
}
0 голосов
/ 08 мая 2018

Вы можете использовать JsonAnySetter аннотацию, которая аннотирует метод, используемый для чтения всех свойств объекта:

class MultiNamedProperties {

    private List<Property> properties = new ArrayList<>();

    @JsonAnySetter
    public void readProperty(String property, Number value) {
        String[] names = property.split(":");
        properties.add(new Property(names[0], names[1], value));
    }

    @Override
    public String toString() {
        return "MultiNamedProperties{" +
                "properties=" + properties +
                '}';
    }
}

class Property {

    private final String first;
    private final String second;
    private final Number value;

    Property(String first, String second, Number value) {
        this.first = first;
        this.second = second;
        this.value = value;
    }

    @Override
    public String toString() {
        return "MyClass{" +
                "first='" + first + '\'' +
                ", second='" + second + '\'' +
                ", value=" + value +
                '}';
    }
}

Вы можете использовать его, как показано ниже:

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.ArrayList;
import java.util.List;

    public class Main {

        public static void main(String[] args) throws Exception {
            String data = "{\"x:y\":1,\"x:z\":2,\"u:v\":3}";
            ObjectMapper mapper = new ObjectMapper();
            MultiNamedProperties mnp = mapper.readValue(data, MultiNamedProperties.class);
            System.out.println(mnp);
        }
    }

Пример печати выше:

MultiNamedProperties{properties=[MyClass{first='x', second='y', value=1}, MyClass{first='x', second='z', value=2}, MyClass{first='u', second='v', value=3}]}

Для этого решения требуется только одна аннотация и два объекта.

0 голосов
/ 07 мая 2018

Не уверен, сможет ли Джексон проанализировать вашу структуру данных, используя только встроенные аннотации и классы. Когда мне нужно добавить логику при разборе json, я всегда пишу собственный десериализатор:

public void loadJsonObjectAsArray() throws IOException {
    String data = "{\"x:y\":1,\"x:z\":2,\"u:v\":3}";
    ObjectMapper mapper = new ObjectMapper();
    Wrapper wrapper = mapper.readValue(data, Wrapper.class);
    List<MyClass> out = wrapper.values;
    System.out.println(out);
}

public static class WrapperDeserializer extends StdDeserializer<Wrapper> {
    public WrapperDeserializer() {
        this(null);
    }

    public WrapperDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Wrapper deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        JsonNode node = jp.getCodec().readTree(jp);
        ObjectNode obj = (ObjectNode) node;
        List<MyClass> parsedFields = new ArrayList<>();

        obj.fields().forEachRemaining(fieldAndNode -> {
            String fieldName = fieldAndNode.getKey();
            Number value = fieldAndNode.getValue().numberValue();
            parsedFields.add(new MyClass(fieldName, value));
        });

        return new Wrapper(parsedFields);
    }
}

private static class MyClass {
    String first;
    String second;
    Number value;

    public MyClass(String both, Number value) {
        String[] split = both.split(":");
        first = split[0];
        second = split[1];
        this.value = value;
    }

    @Override
    public String toString() {
        return "MyClass{" +
                "first='" + first + '\'' +
                ", second='" + second + '\'' +
                ", value=" + value +
                '}';
    }
}

@JsonDeserialize(using = WrapperDeserializer.class)
private static class Wrapper {
    private final List<MyClass> values;

    public Wrapper(List<MyClass> values) {
        this.values = new ArrayList<>(values);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...