如何在Perl中合并两个复杂的数据结构?

时间:2013-08-20 17:31:03

标签: json perl data-structures

我有一个格式化为JSON的配置文件,我需要将其加载到Perl中,然后使用从单独的JSON文件加载的新设置部分覆盖/扩展。可能添加/更改的特定设置各不相同,因此我希望尽可能灵活地使用此覆盖。

我的计划是在使用decode_json之后将新的部分配置合并到现有的完整配置对象中 - 使用JSON或Perl嵌套数据结构。转换效果很好。

有没有一种简单有效的方法在Perl中执行此操作,除了递归遍历我的复杂数据结构并进行大量特定比较之外?我查看了Hash :: Merge,它正在破坏我的数据。问题似乎是它查看高级键/值对(下面的“config”或“bookToolbar”)并覆盖该高级别的完整键/值对。我想要的是搜索深度优先并覆盖它可以使用的最具体的值,同时保持原始的其他键/值对。

例如,这是一个“完整”配置:

{
    "config" : {
        "bookToolbar" : {
            "highlights" : {
                "enabled" : false
            },
            "bookmark" : {
                "enabled" : false
            }
        },
        "pageAspectRatio" : {
            "width" : "432",
            "height" : "648"
        },
        "highlighter" : {
            "sharedColor" : "#000000",
            "colors" : [
                "#ffff00"
            ]
        }
        "mainMenu" : {
            "index" : {
                "dataPath" : "data/index/",
                "enabled" : false
            },
            "media" : {
                "dataPath" : "data/media.xml",
                "enabled" : false
            },
            "toc" : {
                "dataPath" : "data/toc.xml"
            },
            "glossary" : {
                "audioPath" : "audio/glossary/",
                "dataPath" : "data/glossary.xml",
                "imagePath" : "img/glossary/",
                "enabled" : false
            }
        }
    },
    "pagelist" : [{
            "hasOnPageNotes" : true,
            "pageName" : "cover",
            "hasScreenReader" : false,
            "hasTextMarkup" : true,
            "hasLinks" : false,
            "pageId" : "cover"
        }, {
            "hasOnPageNotes" : true,
            "pageName" : "1",
            "hasScreenReader" : false,
            "hasTextMarkup" : true,
            "hasLinks" : false,
            "pageId" : "1"
        }
    ]
}

以下是我想用来部分覆盖/扩展上述内容的数据:

{
    "config" : {
        "bookToolbar" : {
            "bookmark" : {
                "enabled" : true
            },
            "help" : {
                "data" : {
                    "url" : "aGreatHelpFile.html"
                },
                "enabled" : true
            },
            "links" : {
                "enabled" : true
            }
        }
    },
    "pagelist" : [{
            "hasOnPageNotes" : true,
            "pageName" : "2",
            "hasScreenReader" : false,
            "hasTextMarkup" : true,
            "hasLinks" : false,
            "pageId" : "2"
        }
    ]
}

我想要的输出是:

{
    "config" : {
        "bookToolbar" : {
            "highlights" : {
                "enabled" : false
            },
            "help" : {
                "data" : {
                    "url" : "aGreatHelpFile.html"
                },
                "enabled" : true
            },
            "bookmark" : {
                "enabled" : true
            }
            "links" : {
                "enabled" : false
            }
        },
        "pageAspectRatio" : {
            "width" : "432",
            "height" : "648"
        },
        "highlighter" : {
            "sharedColor" : "#000000",
            "colors" : [
                "#ffff00"
            ]
        },
        "mainMenu" : {
            "index" : {
                "dataPath" : "data/index/",
                "enabled" : false
            },
            "media" : {
                "dataPath" : "data/media.xml",
                "enabled" : false
            },
            "toc" : {
                "dataPath" : "data/toc.xml"
            },
            "glossary" : {
                "audioPath" : "audio/glossary/",
                "dataPath" : "data/glossary.xml",
                "imagePath" : "img/glossary/",
                "enabled" : false
            }
        }
    },
    "pagelist" : [{
            "hasOnPageNotes" : true,
            "pageName" : "cover",
            "hasScreenReader" : false,
            "hasTextMarkup" : true,
            "hasLinks" : false,
            "pageId" : "cover"
        }, {
            "hasOnPageNotes" : true,
            "pageName" : "1",
            "hasScreenReader" : false,
            "hasTextMarkup" : true,
            "hasLinks" : false,
            "pageId" : "1"
        }, {
            "hasOnPageNotes" : true,
            "pageName" : "2",
            "hasScreenReader" : false,
            "hasTextMarkup" : true,
            "hasLinks" : false,
            "pageId" : "2"
        }
    ]
}

1 个答案:

答案 0 :(得分:4)

事实证明,Hash::Merge仅使用过时版本的Clone模块在Windows上破坏我的数据。

使用最新版本(或立即在我的Mac上),以下代码完全符合我的需要:

#!/usr/bin/env perl -w
use strict;
use JSON;
use Hash::Merge qw( merge );
Hash::Merge::set_behavior('RIGHT_PRECEDENT');

# Load full config into hashref
open (IN, "<:utf8", "full-config.txt");
my $app_data;
while(<IN>) {$app_data .= $_;}
my $app_json = decode_json($app_data);
close IN;

# Sample portion of config options to override/extend
my $app_override = '{"config": {
                        "bookToolbar": {
                            "bookmark": {
                                "enabled":false
                            }, "help": {
                                "data": {
                                    "url":"aGreatHelpFile.html"
                                }, "enabled":true
                            }, "closeBook": {
                                "enabled":true
                            }
                        }
                    },
                    "pagelist":[
                        {
                             "hasOnPageNotes" : true,
                             "pageName" : "25",
                             "hasTextMarkup" : true,
                             "hasScreenReader" : false,
                             "hasLinks" : false,
                             "pageId" : "0025"
                        }
                    ]
                }';
my $app_override_hash = from_json($app_override, {utf8 => 1});

# Merge with right precedent, $app_json hash ref has everything we need.
$app_json = merge( $app_json, $app_override_hash );

我发现此表非常有助于分析Hash::Merge中的不同优先级选项(这是straight from the docs

    LEFT TYPE   RIGHT TYPE      LEFT_PRECEDENT       RIGHT_PRECEDENT
     SCALAR      SCALAR            $a                   $b
     SCALAR      ARRAY             $a                   ( $a, @$b )
     SCALAR      HASH              $a                   %$b
     ARRAY       SCALAR            ( @$a, $b )          $b
     ARRAY       ARRAY             ( @$a, @$b )         ( @$a, @$b )
     ARRAY       HASH              ( @$a, values %$b )  %$b 
     HASH        SCALAR            %$a                  $b
     HASH        ARRAY             %$a                  ( values %$a, @$b )
     HASH        HASH              merge( %$a, %$b )    merge( %$a, %$b )

    LEFT TYPE   RIGHT TYPE  STORAGE_PRECEDENT   RETAINMENT_PRECEDENT
     SCALAR      SCALAR     $a                  ( $a ,$b )
     SCALAR      ARRAY      ( $a, @$b )         ( $a, @$b )
     SCALAR      HASH       %$b                 merge( hashify( $a ), %$b )
     ARRAY       SCALAR     ( @$a, $b )         ( @$a, $b )
     ARRAY       ARRAY      ( @$a, @$b )        ( @$a, @$b )
     ARRAY       HASH       %$b                 merge( hashify( @$a ), %$b )
     HASH        SCALAR     %$a                 merge( %$a, hashify( $b ) )
     HASH        ARRAY      %$a                 merge( %$a, hashify( @$b ) )
     HASH        HASH       merge( %$a, %$b )   merge( %$a, %$b )