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 )