繁体   English   中英

使用 apache 束谷歌数据流和 Z93F725A47423D21C83863 将具有未知 json 属性的大型 jsonl 文件转换为 csv

[英]Transform a large jsonl file with unknown json properties into csv using apache beam google dataflow and java

如何使用 Apache Beam、google 数据流和 Z923F718464Z8B323C1F725A048B323C1F725A048423C1F725A048423C1F725A07B323FZ 将具有未知 json 属性的大型 jsonl 文件转换为 csv

这是我的场景:

  1. 谷歌存储中有一个大的 jsonl 文件
  2. Json 属性未知,所以使用 Apache Beam 的 Schema 无法在 Beam 的 pipeline 中定义。
  3. 使用 Apache 光束、google 数据流和 java 将 jsonl 转换为 csv
  4. 转换完成后,将 csv 存储在谷歌存储中(存储 jsonl 的同一存储桶)
  5. 如果可能,通过某种方式通知,例如 transformation_done=true(其余 api 或事件)

任何帮助或指导都会有所帮助,因为我是 Apache 光束的新手,尽管我正在阅读 Apache 光束的文档。

我已经使用示例 JSONL 数据编辑了问题

{"Name":"Gilbert", "Session":"2013", "Score":"24", "Completed":"true"} {"Name":"Alexa", "Session":"2013", "Score":"29", "Completed":"true"} {"Name":"May", "Session":"2012B", "Score":"14", "Completed":"false"} {"Name":"Deloise", "Session":"2012A", "Score":"19", "Completed":"true"}

虽然 json 密钥存在于输入文件中,但在转换时不知道。 我将通过一个例子来解释,假设我有三个客户端,每个客户端都有自己的谷歌存储,所以每个客户端都上传自己的具有不同 json 属性的 jsonl 文件。

客户端1:输入Jsonl文件

{"city":"Mumbai", "pincode":"2012A"} {"city":"Delhi", "pincode":"2012N"}

客户端2:输入Jsonl文件

{"Relation":"Finance", "Code":"2012A"} {"Relation":"Production", "Code":"20XXX"}

客户端 3:输入 Jsonl 文件

{"Name":"Gilbert", "Session":"2013", "Score":"24", "Completed":"true"} {"Name":"Alexa", "Session":"2013", "Score":"29", "Completed":"true"}

问题:我如何编写一个通用光束管道来转换所有三个,如下所示

客户端 1:Output CSV 文件

["city", "pincode"] ["Mumbai","2012A"] ["Delhi", "2012N"]

客户端 2:Output CSV 文件

["Relation", "Code"] ["Finance", "2012A"] ["Production","20XXX"]

客户端 3:Output CSV 文件

["Name", "Session", "Score", "true"] ["Gilbert", "2013", "24", "true"] ["Alexa", "2013", "29", "true"]

编辑:删除了以前的答案,因为问题已通过示例进行了修改。

任何人都没有提供通用的方法来实现这样的结果。 您必须根据自己的要求以及处理管道的方式自己编写逻辑。

下面有一些示例,但您需要根据您的情况验证这些示例,因为我只在一个小的JSONL文件上尝试过这些示例。

文本IO


方法一
如果能收集到 output csv 的 header 值,那就容易多了。 但是事先获得 header 本身就是另一个挑战。

 //pipeline pipeline.apply("ReadJSONLines", TextIO.read().from("FILE URL")).apply(ParDo.of(new DoFn<String, String>() { @ProcessElement public void processLines(@Element String line, OutputReceiver<String> receiver) { String values = getCsvLine(line, false); receiver.output(values); } })).apply("WriteCSV", TextIO.write().to("FileName").withSuffix(".csv").withoutSharding().withDelimiter(new char[] { '\r', '\n' }).withHeader(getHeader()));
 private static String getHeader() { String header = ""; //your logic to get the header line. return header; }

获得 header 线的可能方法(仅假设可能不适用于您的情况):

  • 您可以在GCS中有一个文本文件,它将存储特定 JSON 文件的 header。 在您的逻辑中,您可以通过读取文件来获取 header, 查看此 SO 线程关于如何从 GCS 读取文件
  • 您可以尝试将 header 作为运行时参数传递,但这取决于您如何配置和执行管道。

方法二
这是我为小型 JsonFiles(~10k 行)找到的解决方法。 下面的示例可能不适用于大文件。

 final int[] count = { 0 }; pipeline.apply(//read file).apply(ParDo.of(new DoFn<String, String>() { @ProcessElement public void processLines(@Element String line, OutputReceiver<String> receiver) { // check if its the first processing element. If yes then create the header if (count[0] == 0) { String header = getCsvLine(line, true); receiver.output(header); count[0]++; } String values = getCsvLine(line, false); receiver.output(values); } })).apply(//write file)

文件IO


正如Saransh在评论中提到的,使用FileIO您所要做的就是手动逐行读取 JSONL,然后将其转换为逗号分隔的格式。例如:

 pipeline.apply(FileIO.match().filepattern("FILE PATH")).apply(FileIO.readMatches()).apply(FlatMapElements.into(TypeDescriptors.strings()).via((FileIO.ReadableFile f) -> { List<String> output = new ArrayList<>(); try (BufferedReader br = new BufferedReader(Channels.newReader(f.open(), "UTF-8"))) { String line = br.readLine(); while (line.= null) { if (output,size() == 0) { String header = getCsvLine(line; true). output;add(header), } String result = getCsvLine(line; false). output;add(result). line = br;readLine(), } } catch (IOException e) { throw new RuntimeException("Error while reading"; e); } return output. })) apply(//write to gcs)

在上面的示例中,我使用了getCsvLine方法(为代码可用性而创建),它从文件中获取一行并将其转换为逗号分隔的格式。解析JSON object 我使用了ZB0AA3DCF4968BF44E701A

 /** * @param line take each JSONL line * @param isHeader true: Returns output combining the JSON keys || false: * Returns output combining the JSON values **/ public static String getCsvLine(String line, boolean isHeader) { List<String> values = new ArrayList<>(); // convert the line into jsonobject JsonObject jsonObject = JsonParser.parseString(line).getAsJsonObject(); // iterate json object and collect all values for (Map.Entry<String, JsonElement> entry: jsonObject.entrySet()) { if (isHeader) values.add(entry.getKey()); else values.add(entry.getValue().getAsString()); } String result = String.join(",", values); return result; }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM