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

时间:2023-01-15 12:41:18

I have a config file formatted as JSON that I need to load into Perl and then partially override/extend with new settings loaded from a separate JSON file. The particular settings that might be added/changed vary, so I want to make this override as flexible as I can.

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

My plan is to merge a new partial config into the existing full config object - either in JSON or as a Perl nested data structure after using decode_json. That conversion works fine.

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

Is there a simple and efficient way to do this in Perl, short of recursively walking through my complex data structure and making a lot of specific comparisons? I've looked into Hash::Merge and it is mangling my data. The issue seems to be that it looks at high level key/value pairs ("config" or "bookToolbar" below) and overrides the full key/value pair at that high level. What I want is to search depth-first and override the most specific values that it can, while keeping the other key/value pairs from the original.

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

For example, here is a "full" config:

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

{
    "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"
        }
    ]
}

And here is the data I want to use to partially override/extend the above:

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

{
    "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"
        }
    ]
}

My desired output would be:

我想要的输出是:

{
    "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 个解决方案

#1


4  

So it turns out Hash::Merge was only mangling my data on Windows using an outdated version of the Clone module.

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

With up-to-date versions (or on my Mac right off the bat) the following code does exactly what I need:

使用最新版本(或立即在我的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 );

I found this table exceptionally helpful to analyze the different precedence options in Hash::Merge (this is straight from the docs:

我发现这个表非常有助于分析Hash :: Merge中的不同优先级选项(这直接来自文档:

    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 )

#1


4  

So it turns out Hash::Merge was only mangling my data on Windows using an outdated version of the Clone module.

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

With up-to-date versions (or on my Mac right off the bat) the following code does exactly what I need:

使用最新版本(或立即在我的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 );

I found this table exceptionally helpful to analyze the different precedence options in Hash::Merge (this is straight from the docs:

我发现这个表非常有助于分析Hash :: Merge中的不同优先级选项(这直接来自文档:

    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 )