尝试从 Java 中存在模式偏差的数千 JSON 文件中提取数据

Trying to extract data from several thousand JSON files in Java with schema deviations

我有几千个 JSON 文件。他们中的大多数人可以有一个 JSON 数组,数组内有多达 10,000 个元素……更有趣的是,元素的数据结构可能因元素而异……有时只是一个简单的 属性 偏离标准到在每个元素中添加更多数组的偏差。但是我需要从每个文件中提取的是这个“项目”数组。

解决这个问题的方法——在我的逻辑中是——首先从所有文件中提取每一个不同的数据结构,这样我在尝试获取时就明白我要做什么数据。如果我不能命名我想要的元素,那我怎么能得到它们呢?虽然实际上可能有一种方法可以做到这一点,但我对 JSON 和 GSON 等知识还不够了解,无法知道一种方法。

这也将是我的第一个真正的 JSON 项目......我以前从未玩过 JSON 所以我花了很多时间谷歌搜索和阅读,我肯定了解 - 现在 - JSON 是如何工作的......我只是没有能力使用它来发挥任何效果。在过去的几天里,我一直在研究这些文件,虽然我已经取得了一些进展,但我足够聪明,知道什么时候我已经到了需要以前做过这件事的人的帮助的地步。

这些示例不是从这些文件中剪切和粘贴的。为了简单起见,我将它们设为通用。但这是我到目前为止所看到的一个文件与下一个文件的结构差异的示例。第一个文件是迄今为止最常见的...其中“items”数组将具有具有完全相同元素名称的静态结构,但文件中将有 10,000 个...而下一个文件不会好干净

我在这些文件中看到的最常见 JSON 文件:

{
  "employees" : [
    {
      "name": "John Doe"
    },
    {
      "name": "Jane Doe"
    }
  ],
  "items": [
    {
      "item_name": "Goofy Widget",
      "timestamp": 1616987224024,
      "contents": "Some really nice goofy widgets",
      "item_type": "Cleaning Widget",
      "for_sale": false
    },
    {
      "item_name": "Machine Widget",
      "timestamp": 1616987218652,
      "contents": "Hand held vaccuum",
      "item_type": "Functional Widget",
      "for_sale": false
    }
  ],
  "items_from_inventory": true,
  "category_type": "Average",
  "region_placement": "Northwest America"
}

并且手动查看了几个文件,有些文件可能看起来像这样,有时从一个完整的数组元素到下一个元素存在偏差:

{
  "employees" : [
    {
      "name": "Jack Smith"
    },
    {
      "name": "Joe Smith"
    },
    {
      "name": "Jimmy Smalley"
    }
  ],
  "items": [
    {
      "item_name": "Sneakers",
      "timestamp": 1616987224024,
      "contents": "Plain white sneakers",
      "item_type": "Foot Wear",
      "for_sale": false
    },
    {
      "item_name": "Personal T-Shirts",
      "timestamp": 1616987224024,
      "contents": "Color variety T-Shirts",
      "color_options": [
        {
          "color1": "Red",
          "color2": "Green",
          "color3": "Black",
          "color4": "White"
        }
      ],
      "item_classifications": [
        {
          "class1": "Weekend Use",
          "class2": "Family Picnics",
          "class3": "Casual Fridays"
        },
      ],
      "for_sale": false
    },
    {
      "item_name": "Basketballs",
      "timestamp": 1616987218652,
      "contents": "Three quality basketballs",
      "item_type": "Sport Items",
      "brands": [
        {
        "brand1": ",Spalding",
        "brand2": "Wilson"
        },
      ],
      "for_sale": false
    }
  ],
  "items_from_inventory": false,
  "category_type": "Personal Use",
  "region_placement": "North America"
}

这些文件的基本核心结构从一个文件到另一个文件都相当一致,偏差似乎主要在“items”数组中,其中一些元素具有不同的数据结构(我们在MySql世界)比其他人。

我一直在尝试 GSON,因为它似乎相当受欢迎,尽管我不关心我使用的是什么库,我只需要获取数据。

我决定从我目前看到的最常见的数组结构开始,这就是我想到的。下面是代表最常见数组结构的class:

package widgets;

public class Widget {
    
    public Widget(String itemName, long timestamp, String contents, String itemType, boolean forSale) {
        this.itemName     = itemName;
        this.timestamp    = timestamp;
        this.contents     = contents;
        this.itemType     = itemType;
        this.forSale      = forSale;
    }

    private String             itemName;
    private long               timestamp;
    private String             contents;
    private String             itemType;
    private boolean            forSale;

    public void setItemName(String itemName) { this.itemName = itemName;}

    public void setTimestamp(long timestamp) { this.timestamp = timestamp;}

    public void setContents(String contents) { this.contents = contents;}

    public void setItemType(String itemType) { this.itemType = itemType;}

    public void setForSale(boolean forSale)  { this.forSale = forSale;}

    public String getItemName() { return itemName;}

    public long getTimestamp()  { return timestamp;}

    public String getContents() { return contents;}

    public String getItemType() { return itemType;}

    public boolean isForSale()  { return forSale;}

    @Override
    public String toString() {
        return "senderName = " + this.itemName + "\n" +
               "timestamp = " + this.timestamp + "\n" +
               "content = " + this.contents + "\n" +
               "type = " + this.itemType + "\n" +
               "isUnsent = " + (this.forSale ? "true" : "false") + "\n";
    }
}

我有点想把它留在这里,而不是真正进入我成功的地方和我失败的地方,因为我并不真正关心我做错了什么,我只需要知道如何做对...这就是我要的:

谁能告诉我如何从这些文件中提取所有 Json 结构定义,包括每个“items”元素中可能随机出现的不同结构?

考虑到“items”数组的结构可以从一个元素到下一个元素,有人可以告诉我如何正确提取数据吗?

我只需要一个以前来过这里并能为我指明正确道路的人,这样我就不必走每条路,转身走回去然后再尝试另一条路。

非常感谢您的帮助。

谢谢,

迈克·西姆斯

要从 JSON 字符串中提取 JSON 数组,然后将 JSON 数组转换为 Widget 对象,您可以这样做:

JSONObject mainObj = new JSONObject(<full json string>);
JSONArray itemsArr = mainObj.getJSONArray("items");
ObjectMapper om = new ObjectMapper();
List<Widget> widgetList = objectMapper.readValue(itemsArr.toString(), new 
TypeReference<List<Widget>>(){});
// work with widgetList here

我建议使用更轻量级和更具交互性的东西来对数据的“形状”进行探索性数据分析。我的两个最佳选择是:

取决于您更习惯使用交互式图形工具还是命令行工具。两者都是开源且免费的,可让您快速探索您的数据集。

我体会到了GSON的强大!

我最终弄清楚了如何将 JSON 数据映射到 classes。我首先遍历所有文件并使用如下方法提取每个元素名称:

private void getElements(String path){
        try {
            Reader reader = Files.newBufferedReader(Paths.get(path));
            JsonObject jo = JsonParser.parseReader(reader).getAsJsonObject();
            for (String key : jo.keySet()) {
                System.out.println(key);
            }
        }
        catch (IOException e) {e.printStackTrace();}
}

这最终给了我:

employees
items
items_from_inventory
category_type
region_placement

我已经知道了,但是该方法使我能够验证每个文件在主元素方面是否相同。

然后,知道 items 是一个数组,我再次遍历每个文件并使用此方法从每个数组中获取元素,只是我决定不打印到控制台,而是创建一个数组列表来保存每个结构每个数组仅当该结构是唯一的时:

private final List<LinkedList<String>> arraySets = new ArrayList<>();
private void getArrayElements(String path) {
    try {
        Reader reader = Files.newBufferedReader(Paths.get(path));
        JsonObject jo = JsonParser.parseReader(reader).getAsJsonObject();
        JsonArray ja = jo.getAsJsonArray("items");
        int max = ja.size();
        for (int x = 0; x < max; x++) {
            JsonElement je = ja.set(x,ja.get(x));
            JsonObject njo = je.getAsJsonObject();
            LinkedList<String> arraySet = new LinkedList<>();
            for(String key: njo.keySet()) {
                arraySet.addLast(key);
            }
            if (!arraySets.contains(arraySet)) {
                arraySets.add(arraySet);
            }
        }
    }
    catch (IOException e) { e.printStackTrace();}
}

以同样的方式,我遍历了数组中的每个数组并提取了那些元素名称。

然后,我创建了一个母版 class,其中包含适合每个文件大纲的变量,因为每个文件都包含完全相同的元素 - 当然唯一的区别是每个“项目”数组中可以有不同的元素。

因此,整个文件的主 class 看起来像这样:

import Item;
import Employee;
import java.util.List;

public class Master {

    private List<Employee> employees;
    private List<Item>     items;
    private boolean items_from_inventory;
    private String category_type;
    private String region_placement;

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }

    public boolean isItems_from_inventory() {
        return items_from_inventory;
    }

    public void setItems_from_inventory(boolean items_from_inventory) {
        this.items_from_inventory = items_from_inventory;
    }

    public String getCategory_type() {
        return category_type;
    }

    public void setCategory_type(String category_type) {
        this.category_type = category_type;
    }

    public String getRegion_placement() {
        return region_placement;
    }

    public void setRegion_placement(String region_placement) {
        this.region_placement = region_placement;
    }
}

然后员工 class 看起来像这样:

public class Employee {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

和 Items class 类似,只是它有额外的 List 变量,并且每个列表类型都是它自己的 class,它具有要映射到的数组的独立元素。我包括了所有可能包含在项目数组中的数组。

然后我就这样做了:

Path         rootDir  = Paths.get(rootFolderString);
Stream<Path> paths    = Files.walk(rootDir);
List<Path>   pathList = paths.collect(Collectors.toList());
for (Path path : pathList) {
    if (path.toFile().getAbsolutePath().endsWith("json")) {
        String fileString = new String(Files.readAllBytes(path.toFile().getAbsolutePath()));
        Master master = new Gson().fromJson(fileString,new TypeToken<Master>() {}.getType());
    }
}

而且效果非常好!