【问题标题】:Unable to deserialize complex/dynamic JSON to Java classes using Gson无法使用 Gson 将复杂/动态 JSON 反序列化为 Java 类
【发布时间】:2020-08-06 16:21:39
【问题描述】:

我一直在尝试使用 Gson 将 JSON 反序列化为 Java 类,但 JSON 结构对我来说太复杂了,无法处理。 JSON 看起来像这样(由于重复,我已经修剪了一些):

{
   "results":[
      {
         "openEHR-EHR-CLUSTER.encounter_channel.v0/items[at0001]/value<DV_TEXT>":{
            "type":"DV_TEXT",
            "name":{
               "en":"Encounter channel"
            },
            "attrs":[
               "value"
            ]
         },
         "openEHR-EHR-CLUSTER.monitoring_reason.v0/items[at0001]/value<DV_TEXT>":{
            "type":"DV_TEXT",
            "name":{
               "en":"Monitoring reason"
            },
            "attrs":[
               "value"
            ]
         }
      },
      {
         "163eee06-83a4-4fd8-bf65-5d6a3ef35ac5":{
            "d5760d01-84dd-42b2-8001-a69ebaa4c2df":{
               "date":"2020-08-06 09:45:31",
               "cols":[
                  {
                     "type":"DV_TEXT",
                     "path":"openEHR-EHR-CLUSTER.encounter_channel.v0/items[at0001]/value<DV_TEXT>",
                     "values":[
                        {
                           "instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.encounter_channel.v0](0)/items[at0001](0)/value",
                           "value":"null"
                        }
                     ]
                  },
                  {
                     "type":"DV_TEXT",
                     "path":"openEHR-EHR-CLUSTER.monitoring_reason.v0/items[at0001]/value<DV_TEXT>",
                     "values":[
                        {
                           "instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.monitoring_reason.v0](1)/items[at0001](0)/value",
                           "value":"null"
                        }
                     ]
                  }
               ]
            },
            "fb366b72-d567-4d23-9f5f-356fc09aff6f":{
               "date":"2020-08-06 10:02:26",
               "cols":[
                  {
                     "type":"DV_TEXT",
                     "path":"openEHR-EHR-CLUSTER.encounter_channel.v0/items[at0001]/value<DV_TEXT>",
                     "values":[
                        {
                           "instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.encounter_channel.v0](0)/items[at0001](0)/value",
                           "value":"Consulta presencial"
                        }
                     ]
                  },
                  {
                     "type":"DV_TEXT",
                     "path":"openEHR-EHR-CLUSTER.monitoring_reason.v0/items[at0001]/value<DV_TEXT>",
                     "values":[
                        {
                           "instanceTemplatePath":"prova_de_conceito.en.v1/context/other_context[at0001]/items[archetype_id=openEHR-EHR-CLUSTER.monitoring_reason.v0](1)/items[at0001](0)/value",
                           "value":"Consulta"
                        }
                     ]
                  }
               ]
            }
         }
      }
   ],
   "pagination":{
      "max":20,
      "offset":0,
      "nextOffset":20,
      "prevOffset":0
   },
   "timing":"475 ms"
}

主要的 JSON 对象具有三个字段:resultspaginationtiming。我可以反序列化 paginationtiming 就好了,因为它们总是具有相同的结构。我无法正确反序列化 results

results 始终是两个不同对象的列表。特别是第二个对象是最复杂的,因为它的字段名称不是静态的。 UUID 名称引用总是在每个 API 响应上发生变化。例如,名为 "163eee06-83a4-4fd8-bf65-5d6a3ef35ac5" 的字段可能在下一个 JSON 响应中具有另一个 id。因此,我无法在相应的 Java 类中给它一个正确的字段名称。在这种情况下,"d5760d01-84dd-42b2-8001-a69ebaa4c2df""fb366b72-d567-4d23-9f5f-356fc09aff6f" 也是如此。

关于如何使用 Gson 正确反序列化这种 JSON 有什么想法吗?我尝试了几种不同的方法,但到目前为止都没有真正奏效。

在最近的尝试中,我尝试使用 JsonDeserializer 方法来区分 results 列表中的对象类型。我当前的实现看起来像这样(getter 和 setter 因为空间而被隐藏):

QueryResponse.java

public class QueryResponse {
    private List<Map<String, ResultInterface>> results;
    private Pagination pagination;
    private String timing;
}

分页.java

public class Pagination {
    private Integer max;
    private Integer offset;
    private Integer nextOffset;
    private Integer previousOffset;
}

ResultInterface.java

public interface ResultInterface {

}

ElementDefinition.java

public class ElementDefinition implements ResultInterface {
    private String type;
    private Name name;
    private List<String> attrs;
}

名称.java

public class Name {
    private String en;
    private String es;
}

Compositions.java

public class Compositions implements ResultInterface {
    private Map<String, Composition> compositions;
}

Composition.java

public class Composition {
    private String date;
    private List<Col> cols;
}

Col.java

public class Col {
    private String type;
    private String path;
    private List<Value> values;
}

值.java

public class Value {
    private String instanceTemplatePath;
    private String value;
    private String magnitude;
    private String units;
    private String code;
    private String terminology_id;
}

ResultInterfaceDeserializer.java

public class ResultInterfaceDeserializer implements JsonDeserializer<ResultInterface> {
    
    @Override
    public ResultInterface deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jObject = (JsonObject) json;
        JsonElement typeObj = jObject.get("type");

        if (typeObj != null) {
            return context.deserialize(json, ElementDefinition.class);
        } else {
            return context.deserialize(json, Compositions.class);
        }
    }
}

我这样称呼 Gson:

GsonBuilder builder = new GsonBuilder();
        
builder.registerTypeAdapter(ResultInterface.class, new ResultInterfaceDeserializer());
Gson gson = builder.create();
        
QueryResponse queryResponse = gson.fromJson(externalJsonResponse, QueryResponse.class);

这个实现的问题是在 JSON 结构中没有名为 compositions 的东西,因此无法正确识别 Compositions.java 类。我知道我必须使用像Map&lt;String, SomeObject&gt; 这样的Java 结构,但问题是这里有太多动态命名的Json 字段,如果它们没有固定的名称标识符,我无法“抓取”它们。

更新

我设法找到了解决方案。我想说这实际上是一种解决方法,可能不是最干净或优雅的解决方案。 我当前实现的问题是我试图“抓取”一个名为 compositions 的 JSON 字段,而实际上它并不存在。因此,我决定自己操作 JSON 并添加该字段(在代码中)。 我将反序列化器类更改为:

public class ResultInterfaceDeserializer implements JsonDeserializer<ResultInterface> { 
    public String encloseJsonWithCompositionsField(JsonElement json) {
        return "{\"compositions\":" + json.toString() + "}";
    }
    
    @Override
    public ResultInterface deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jObject = (JsonObject) json;
        
        if (jObject.get("type") != null) {
            return context.deserialize(json, ElementDefinition.class);
        } else {
            JsonElement jsonWithCompositionsField = new JsonParser().parse(encloseJsonWithCompositionsField(json));
            return context.deserialize(jsonWithCompositionsField, Compositions.class);
        }
    }
}

通过此更改,我现在可以“抓取”组合字段并获取 Java POJO 中的数据。

【问题讨论】:

  • Map&lt;String, Result&gt;?
  • 您能说得更具体些吗?没有定义 Result 类。
  • 你能从 API 提供者那里得到这个 JSON 的 JsonSchema 吗?这将对您映射这个 JSON 有很大帮助,然后您可以使用这个很棒的站点来转换它(它可能对这个 JSON 也有帮助,但您会被这个动态命名的属性卡住)jsonschema2pojo.org 另外,你可以映射为普通对象并手动完成所有工作(迭代并获取每个属性名称和值等等......)
  • 不,我没有可使用的 JsonSchema。由于动态命名的字段,我不能真正使用该网站自动生成类。

标签: java json gson


【解决方案1】:

您可以通过为Compositions 注册一个额外的 JsonDeserializer 来解决这个问题:

public class CompositionsDeserializer implements JsonDeserializer<Compositions> {
    public static final CompositionsDeserializer INSTANCE = new CompositionsDeserializer();
    
    private CompositionsDeserializer() { }
    
    @Override
    public Compositions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        Compositions compositions = new Compositions();
        Map<String, Composition> compositionsMap = new HashMap<>();
        compositions.compositions = compositionsMap;
        
        JsonObject compositionsJson = json.getAsJsonObject();
        for (Map.Entry<String, JsonElement> compositionEntry : compositionsJson.entrySet()) {
            Composition composition = context.deserialize(compositionEntry.getValue(), Composition.class);
            compositionsMap.put(compositionEntry.getKey(), composition);
        }
        
        return compositions;
    }
}

然后在 GsonBuilder 上注册该反序列化器:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(ResultInterface.class, new ResultInterfaceDeserializer())
    .registerTypeAdapter(Compositions.class, CompositionsDeserializer.INSTANCE)
    .create();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-08
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    相关资源
    最近更新 更多