【问题标题】:XML created from JSON is not correctly nested using perl从 JSON 创建的 XML 未使用 perl 正确嵌套
【发布时间】:2020-06-08 19:18:37
【问题描述】:

我正在尝试使用 perl 脚本将 JSON 数据转换为 XML。但是转换后的 JSON 没有预期的标签。以下是我使用的输入、代码和收到的输出

{"status": "Success",
 "output":
     {"product_artifacts":
         [
             {"variant_name": "test_var",
 "artifacts":
                  [
                      {"artifact_created": "10-25-19 15:52:02",
 "artifact_download_link": "http://abc:rt/ ",
 "artifact_digital_size": 123,
 "artifact_number": "123/234",
 "artifact_revision": "AB1"}
                  ]
              }
         ]
      },
 "message":
     []
 }

当传递给下面的 Perl 脚本时,上面的 Json 没有按预期创建 XML: Perl 脚本:

#!/app/perl/5.16.2/LMWP3/bin/perl

use strict;
use warnings;

binmode STDOUT, ":utf8";
use utf8;



use JSON;
use XML::Simple;

# Read input file in json format
my $json = '
{"status": "Success",
 "output":
     {"product_artifacts":
         [
             {"variant_name": "test_var",
 "artifacts":
                  [
                      {"artifact_created": "10-25-19 15:52:02",
 "artifact_download_link": "http://abc:rt/ ",
 "artifact_digital_size": 123,
 "artifact_number": "123/234",
 "artifact_revision": "AB1"}
                  ]
              }
         ]
      },
 "message":
     []
 }';

# Convert JSON format to perl structures
my $data = decode_json($json);

# Output as XML
print "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
print XMLout($data);
print "\n";

实际输出:

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<opt status="Success">
  <output>
    <product_artifacts variant_name="test_var">
      <artifacts artifact_created="10-25-19 15:52:02" artifact_digital_size="9293792" artifact_download_link="http://abc:rt " artifact_number="123/234" artifact_revision="AC" />
    </product_artifacts>
  </output>
</opt>

预期输出:

<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <status>Success</status>
  <output>
    <product_artifacts>
      <variant_name>test_var</variant_name>
      <artifacts>
        <artifact_created>10-25-19 15:52:02</artifact_created>
        <artifact_download_link>http://asd:rt </artifact_download_link>
        <artifact_digital_size>123</artifact_digital_size>
        <artifact_number>1234</artifact_number>
        <artifact_revision>AC</artifact_revision>
      </artifacts>
    </product_artifacts>
  </output>
  <message/>
</root>

有人可以帮忙解决我哪里出错了

【问题讨论】:

  • XML::Simple 很糟糕。它并不一致,它的文档不鼓励使用它。

标签: json xml perl nested-attributes data-conversion


【解决方案1】:

到目前为止,还没有提供解决方案。

让我提供一种可能的简单解决方案,无需使用任何 perl 模块。

use strict;
use warnings;
use feature 'say';

use JSON;

binmode STDOUT, ":utf8";
use utf8;

my $json = '
{"status": "Success",
 "output":
    { "product_artifacts":
         [
            {
                "variant_name": "test_var",
                "artifacts":
                    [
                        {
                            "artifact_created": "10-25-19 15:52:02",
                            "artifact_download_link": "http://abc:rt/ ",
                            "artifact_digital_size": 123,
                            "artifact_number": "123/234",
                            "artifact_revision": "AB1"
                        }
                    ]
            }
         ]
    },
    "message":[]
 }';

# Convert JSON format to perl structures
my $data = decode_json($json);

say json2xml($data);

sub json2xml {
    my $data  = shift;

    my $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";

    $xml .= "<root>\n";
    $xml .= j2x($data,1);
    $xml .= "</root>\n";

    return $xml;
}

sub j2x {
    my $json  = shift;
    my $depth = shift;

    my $xml;
    my $indent = 2;
    my $space = ' ' x ($depth*$indent);

    while( my($k,$v) = each %{$json} ) {
        if( ref $v eq 'HASH' ) {
            $xml .= $space . "<$k>\n";
            $xml .= j2x($v,$depth+1);
            $xml .= $space . "</$k>\n";
        } elsif ( ref $v eq 'ARRAY' ) {
            $xml .= $space . "<$k>\n";
            foreach my $e (@{$v}) {
                $xml .= j2x($e,$depth+1);
            }
            $xml .= $space . "</$k>\n";
        } else {
            $xml .= $space . "<$k>$v</$k>\n";
        }
    }

    return $xml;
}

输出与期望略有不同,因为代码未考虑空 xml 元素(在这种特殊情况下为空 JSON 数组)

<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <status>Success</status>
  <output>
    <product_artifacts>
      <variant_name>test_var</variant_name>
      <artifacts>
        <artifact_number>123/234</artifact_number>
        <artifact_created>10-25-19 15:52:02</artifact_created>
        <artifact_revision>AB1</artifact_revision>
        <artifact_digital_size>123</artifact_digital_size>
        <artifact_download_link>http://abc:rt/ </artifact_download_link>
      </artifacts>
    </product_artifacts>
  </output>
  <message>
  </message>
</root>

注意:在 OP 的帖子中 JSON 和所需的输出不匹配,因此生成的输出是帖子中给出的 JSON 数据的表示

【讨论】:

  • 请不要仅仅因为没有人提供解决方案。此解决方案不会对值进行 XML 转义。正确的解决方案是使用 XML 渲染器或 XML 模板(我的建议是 Text::Xslate 或 Mojo::Template)。
  • @Grinnz -- 发布您的解决方案以供审核。我不太精通 XML 转义值。我看到了输入和期望的输出,混合了一点想象力,代码诞生了,以简单的方式将 input 转换为 output 用于教育目的(那些正在学习 perl 的人) .这段代码无论如何都不适用于公共 CPAN 模块——对我来说,它只是一个小小的大脑练习。
【解决方案2】:

Perl 数据结构不直接映射到 XML。例如,某个位置的 hashref 可以由标签上的属性或嵌套标签表示,这些标签本身可能具有属性、标签或文本。因此,要按照您想要的方式格式化输出,一种方法是使用模板来定义您想要的结构,例如使用Mojo::Template

use strict;
use warnings;
use Mojo::Template;

my $tmpl = <<'TMPL';
<?xml version="1.0" encoding="UTF-8" ?>
<root>
  <status><%= $data->{status} %></status>
  <output>
    <product_artifacts>
    % foreach my $variant (@{$data->{output}{product_artifacts}}) {
      <variant_name><%= $variant->{variant_name} %></variant_name>
      <artifacts>
      % foreach my $artifact (@{$variant->{artifacts}}) {
        % foreach my $key (sort keys %$artifact) {
          <<%= $key %>><%= $artifact->{$key} %></<%= $key %>>
        % }
      % }
      </artifacts>
    % }
    </product_artifacts>
  </output>
  <message/>
</root>
TMPL

my $t = Mojo::Template->new(auto_escape => 1, vars => 1);
my $xml = $t->render($tmpl, {data => $data});

您的预期格式仍然有些不清楚 - 例如,如果您在这些数组中获得多个变体或工件,请考虑应如何布局。这些是自动转换不太可能满足您需要的部分原因。


另一种方法是使用XML遍历工具来适当地构建XML,这有点繁琐但意味着您不需要手动编写标签,Mojo::DOM可以用于此目的。

use strict;
use warnings;
use Mojo::DOM;

my $dom = Mojo::DOM->new->xml(1)->parse('<?xml version="1.0" encoding="UTF-8" ?><root/>');

my $root = $dom->at('root');
$root->append_content($dom->new_tag('status', $data->{status}));
$root->append_content($dom->new_tag('output'));
my $output = $root->at('output');
$output->append_content($dom->new_tag('product_artifacts'));
my $product_artifacts = $output->at('product_artifacts');
foreach my $variant (@{$data->{output}{product_artifacts}}) {
  $product_artifacts->append_content($dom->new_tag('variant_name', $variant->{variant_name}));
  $product_artifacts->append_content($dom->new_tag('artifacts'));
  my $artifacts = $product_artifacts->at('artifacts');
  foreach my $artifact (@{$variant->{artifacts}}) {
    foreach my $key (sort keys %$artifact) {
      $artifacts->append_content($dom->new_tag($key, $artifact->{$key}));
    }
  }
}
$root->append_content($dom->new_tag('message', $data->{message}));

my $xml = $dom->to_string;

这些示例都将 XML 生成为字符;输出到文件或其他文件时应编码为 UTF-8。

【讨论】:

    【解决方案3】:

    注意 XML::Simple 已被弃用,作者本人建议使用其他模块。 但是,我不知道可以使用模块轻松地将数据结构转储到 XML(可能 XML::Dumper 除外,但它具有非常不同的输出结构),而无需“手动”构建数据结构。

    对于所需的输出格式,需要为 XMLOut 函数设置以下选项:

    print XMLout($data,NoAttr => 1, RootName => 'root');
    

    但是,这仍然会留下“消息”标签,它是一个空数组,并且 XML::Simple 似乎默默地丢弃它(耶!)。

    <root>
      <output>
        <product_artifacts>
          <artifacts>
            <artifact_created>10-25-19 15:52:02</artifact_created>
            <artifact_digital_size>123</artifact_digital_size>
            <artifact_download_link>http://abc:rt/ </artifact_download_link>
            <artifact_number>123/234</artifact_number>
            <artifact_revision>AB1</artifact_revision>
          </artifacts>
          <variant_name>test_var</variant_name>
        </product_artifacts>
      </output>
      <status>Success</status>
    </root>
    

    一种粗略的解决方法是将其设置为 undef,并将 SuppressEmpty 参数设置为 undef,但这仍然不会生成相同的输出,因为 XML::Simple 似乎不会生成空标签。

    有关演示和完整代码,请参阅:https://ideone.com/kwqZzo

    或者,您可以使用XML::Writer 手动构造 xml,但这在很大程度上取决于数据。您可以尝试递归方法,但这会很脆弱。对于问题中的 exact json 和 output,您可能会使用如下内容:

    #!/app/perl/5.16.2/LMWP3/bin/perl
    
    use strict;
    use warnings;
    
    binmode STDOUT, ":utf8";
    use utf8;
    
    
    
    use JSON;
    
    use XML::Writer;
    
    # Read input file in json format
    my $json = qq(
    {
      "status": "Success",
      "output": {
        "product_artifacts": [
          {
            "variant_name": "test_var",
            "artifacts": [
              {
                "artifact_created": "10-25-19 15:52:02",
                "artifact_download_link": "http://abc:rt/ ",
                "artifact_digital_size": 123,
                "artifact_number": "123/234",
                "artifact_revision": "AB1"
              }
            ]
          }
        ]
      },
      "message": []
    }
    );
    my $data = decode_json($json);
    my $writer = XML::Writer->new( OUTPUT => 'self',DATA_MODE => 1, DATA_INDENT => 4);
    
    $writer->xmlDecl("UTF-8");
    $writer->startTag('root');
        $writer->dataElement(status => $data->{status});
        $writer->startTag('output');
           for my $p (@{$data->{output}{product_artifacts}}) {
               $writer->startTag('product_artifacts');
               $writer->dataElement($_ => $p->{$_}) for qw(variant_name);
               for my $a (@{$p->{artifacts}}) {
                   $writer->startTag('artifacts');
                   $writer->dataElement($_ => $a->{$_}) for qw(artifact_created
                                                           artifact_download_link
                                                           artifact_digital_size
                                                           artifact_number
                                                           artifact_revision);
                   $writer->endTag('artifacts');
               }
               $writer->endTag('product_artifacts');
           }
        $writer->endTag('output');
        $writer->emptyTag('message');
    $writer->endTag('root');
    
    print $writer->to_string();
    print "\n";
    

    【讨论】:

      猜你喜欢
      • 2015-07-31
      • 1970-01-01
      • 1970-01-01
      • 2013-06-07
      • 1970-01-01
      • 1970-01-01
      • 2021-01-09
      • 1970-01-01
      • 2017-08-20
      相关资源
      最近更新 更多