0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NodeでXML → JSON/CSV変換パッケージを作った話

Last updated at Posted at 2024-12-31

個人的に作ってるアプリで、nintendo-switch-eshopを使っていました。
その中で、発見があってパッケージよりXMLからの方がデータを多く取れるんですよね実は。(メーカー情報とか取れて便利)
そこでJSONとCSVに変換したいと思って作りました。
(探している中で特になかったんですよね、XML久しぶりに見ましたがあんま需要ないのかな(これは燃える))

とりあえずJSONCSVがいいかなと思って作りました。

格納先

パッケージの主な特徴

  • XML to JSON/CSVの変換
  • ネストされたオブジェクトのフラット化
  • カスタマイズ可能な変換オプション

インストール

npm install xml2json-csv

使用例

XML to JSON

import { xmlToJson } from 'xml2json-csv';

const xmlString = `
  <root>
    <item>
      <name>山田太郎</name>
      <age>30</age>
    </item>
  </root>
`;

async function convertExample() {
  try {
    // 基本的な変換
    const jsonData = await xmlToJson(xmlString);
    console.log(jsonData);

    // Specify custom root element
    const customRootData = await xmlToJson(xmlString, {
      rootElement: "<custom root element>",
    });
    console.log(customRootData);
  } catch (error) {
    console.error('変換エラー:', error);
  }
}

XML to CSV

import { xmlToCsv } from 'xml2json-csv';

const xmlString = `
  <root>
    <item>
      <name>山田太郎</name>
      <age>30</age>
    </item>
  </root>
`;

async function convertExample() {
  try {
    // 基本的な変換
    const csvData = await xmlToCsv(xmlString);
    console.log(csvData);

    // カスタムCSVオプション
    const customCsv = await xmlToCsv(xmlString, {
      csvOptions: {
        delimiter: ';'
      }
    });
    console.log(customCsv);
  } catch (error) {
    console.error('変換エラー:', error);
  }
}

実装上の工夫

  • カスタムエレメントの実装
    XMLの自在生を考えたときに自動で判別できるのが一番ですがうまくいかなかったので、指定できるようにしてあります(parserでうまくやればいいのかな)
function xmlToJson(
  xmlString: string,
  options: XmlToJsonOptions = {}
)
  • ネストされた構造の柔軟な処理
    ネストは特に厄介でしたね、XML自体がかなり自在にデータを持てるので面倒だった、、、笑
    再帰関数を使用して、複雑にネストされたXMLデータを効率的に処理しました。
function normalizeStructure(
  obj: any,
  options: { arrayHandling: "preserve" | "concatenate"; arraySeparator: string }
): GenericObject | null {
  if (!obj || typeof obj !== "object") return null;

  const result: GenericObject = {};

  for (const [key, value] of Object.entries(obj)) {
    if (!value) continue;

    if (key === "@" && typeof value === "object") {
      // 属性を平坦化
      Object.entries(value as Record<string, unknown>).forEach(
        ([attrKey, attrValue]) => {
          if (attrValue !== null && attrValue !== undefined) {
            result[`${key}${attrKey}`] = attrValue;
          }
        }
      );
    } else if (Array.isArray(value)) {
      // 配列の処理
      const filteredArray = value.filter(
        (v) => v !== null && v !== undefined && v !== ""
      );
      if (filteredArray.length > 0) {
        if (options.arrayHandling === "concatenate") {
          result[key] = `[${filteredArray.join(options.arraySeparator)}]`;
        } else {
          result[key] = filteredArray
            .map((item) =>
              typeof item === "object"
                ? normalizeStructure(item, options)
                : item
            )
            .filter((item): item is GenericObject => item !== null);
        }
      }
    } else if (typeof value === "object") {
      const normalized = normalizeStructure(value, options);
      if (normalized && Object.keys(normalized).length > 0) {
        result[key] = normalized;
      }
    } else if (value !== "") {
      result[key] = value;
    }
  }

  return Object.keys(result).length > 0 ? result : null;
}

終わりに

XML自体久しく使っていなかったのでちょっと苦戦しました
よかったら使ってみてください!!
あとはGitHubでコード公開してるので、ダメ出しなどいただけたら嬉しいです(Mじゃない)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?