簡體   English   中英

Jackson將JSON對象解析為對象數組

[英]Jackson Parse JSON Object as Array of Objects

我正在嘗試在Jackson中解析以下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;
    }
}

但是我得到一個錯誤:

線程“主”中的異常com.fasterxml.jackson.databind.exc.MismatchedInputException:無法從START_OBJECT令牌中反序列化entry.JacksonObjectTest $ MyClass []

我感覺合理; 我正在嘗試將JSON對象的每個字段解析為對象數組,而Jackson顯然對此不太滿意。 忽略@JsonProperty("both")產生:

線程“主”中的異常com.fasterxml.jackson.databind.exc.InvalidDefinitionException:類型條目的類型定義無效。JacksonObjectTest$ MyClass:參數0沒有屬性名,不可注入:不能用作Creator [條目的構造方法.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;
        }
    }
}

編輯 :如評論中所述,我確實知道使用TypeReference<Map<String,Number>> 這可行,但是我試圖使解析代碼盡可能地包含和通用,並且使用此解決方案意味着我必須在解析后做進一步的轉換以獲得MyClass[] (對Map<String,Number>第一次解析,然后為MyClass[] 有沒有一種方法可以跳過中間人(即:告訴Jackson如何將已知格式的JSON斑點處理為數據類型)?

您可以使用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}]}

該解決方案僅需要一個注釋和兩個對象。

不確定jackson是否可以僅使用內置注釋和類來解析您的數據結構。 當我在解析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);
    }
}

編輯 :提供的其他答案可以更好地回答整個問題。 將保留此位置以為注釋中提到的解決方案提供參考(對於那些只想快速格式化數據的人來說可能更容易)。


這是我目前的解決方案。 我想指出,它沒有解決問題的編輯部分中提到的問題。

注釋中提到了使用的方法:首先解析為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();
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM