我将如何在 Perl 中读取此数据结构? Dictionary/Hash 键包含列表,列表包含列表。 Python::Inline 给我错误

How would I read this data structure in Perl? Dictionary/Hash with keys containing lists containing lists. Python::Inline giving me errors

我已经为这个简单的问题苦苦挣扎了大约 3 周。我不明白为什么,我愿意付出一切来解决这个问题,哈哈。

我正在尝试从下面的数据结构中读取值。文档说它是一个字典,其键包含该类型的结果列表。

示例:我使用 eval 函数获得主查询回复。我查找键 "song_hits" 以获得该结构。然后我查找密钥 'track' 并解析它。问题是进入 'track' 部分。

当我按照 Perl 文档告诉我的方式进行操作时,我得到无法通过包 "Inline::Python::Object::Data" 定位对象方法 "FIRSTKEY"。

所以我想知道是否有一种方法可以使用 eval 函数读取值来绕过 ObjectData 的哈希键限制,另一种读取它的方法,因为我知道确切的键,或者如果我只是这样做完全错误.

{
'album_hits': [
{
    'album':
    {
        'albumArtRef': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fF...',
        'albumArtist': 'J.Cole',
        'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y',
        'artist': 'J.Cole',
        'artistId': ['Ajgnxme45wcqqv44vykrleifpji'],
        'description_attribution':
        {
            'kind': 'sj#attribution',
            'license_title': 'Creative Commons Attribution CC-BY',
            'license_url': 'http://creativecommons.org/licenses/by/4.0/legalcode',
            'source_title': 'Freebase',
            'source_url': ''
        },
        'explicitType': '1',
        'kind': 'sj#album',
        'name': 'Work Out',
        'year': 2011
    },
    'type': '3'
}],
'artist_hits': [
{
    'artist':
    {
        'artistArtRef': 'http://lh3.googleusercontent.com/MJe-cDw9uQ-pUagoLlm...',
        'artistArtRefs': [
        {
            'aspectRatio': '2',
            'autogen': False,
            'kind': 'sj#imageRef',
            'url': 'http://lh3.googleusercontent.com/MJe-cDw9uQ-pUagoLlmKX3x_K...'
        }],
        'artistId': 'Ajgnxme45wcqqv44vykrleifpji',
        'artist_bio_attribution':
        {
            'kind': 'sj#attribution',
            'source_title': 'David Jeffries, Rovi'
        },
        'kind': 'sj#artist',
        'name': 'J. Cole'
    },
    'type': '2'
}],
'playlist_hits': [
{
    'playlist':
    {
        'albumArtRef': [
        {
            'url': 'http://lh3.googleusercontent.com/KJsAhrg8Jk_5A4xYLA68LFC...'
        }],
        'description': 'Workout Plan ',
        'kind': 'sj#playlist',
        'name': 'Workout',
        'ownerName': 'Ida Sarver',
        'shareToken': 'AMaBXyktyF6Yy_G-8wQy8Rru0tkueIbIFblt2h0BpkvTzHDz-fFj6P...',
        'type': 'SHARED'
    },
    'type': '4'
}],
'situation_hits': [
{
    'situation':
    {
        'description': 'Level up and enter beast mode with some loud, aggressive music.',
        'id': 'Nrklpcyfewwrmodvtds5qlfp5ve',
        'imageUrl': 'http://lh3.googleusercontent.com/Cd8WRMaG_pDwjTC_dSPIIuf...',
        'title': 'Entering Beast Mode',
        'wideImageUrl': 'http://lh3.googleusercontent.com/8A9S-nTb5pfJLcpS8P...'
    },
    'type': '7'
}],
'song_hits': [
{
    'track':
    {
        'album': 'Work Out',
        'albumArtRef': [
        {
            'aspectRatio': '1',
            'autogen': False,
            'kind': 'sj#imageRef',
            'url': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fFdxj5w...'
        }],
        'albumArtist': 'J.Cole',
        'albumAvailableForPurchase': True,
        'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y',
        'artist': 'J Cole',
        'artistId': ['Ajgnxme45wcqqv44vykrleifpji', 'Ampniqsqcwxk7btbgh5ycujij5i'],
        'composer': '',
        'discNumber': 1,
        'durationMillis': '234000',
        'estimatedSize': '9368582',
        'explicitType': '1',
        'genre': 'Pop',
        'kind': 'sj#track',
        'nid': 'Tq3nsmzeumhilpegkimjcnbr6aq',
        'primaryVideo':
        {
            'id': '6PN78PS_QsM',
            'kind': 'sj#video',
            'thumbnails': [
            {
                'height': 180,
                'url': 'https://i.ytimg.com/vi/6PN78PS_QsM/mqdefault.jpg',
                'width': 320
            }]
        },
        'storeId': 'Tq3nsmzeumhilpegkimjcnbr6aq',
        'title': 'Work Out',
        'trackAvailableForPurchase': True,
        'trackAvailableForSubscription': True,
        'trackNumber': 1,
        'trackType': '7',
        'year': 2011
    },
    'type': '1'
}],
'station_hits': [
{
    'station':
    {
        'compositeArtRefs': [
        {
            'aspectRatio': '1',
            'kind': 'sj#imageRef',
            'url': 'http://lh3.googleusercontent.com/3aD9mFppy6PwjADnjwv_w...'
        }],
        'contentTypes': ['1'],
        'description': 'These riff-tastic metal tracks are perfect for getting the blood pumping.',
        'imageUrls': [
        {
            'aspectRatio': '1',
            'autogen': False,
            'kind': 'sj#imageRef',
            'url': 'http://lh5.ggpht.com/YNGkFdrtk43e8H941fuAHjflrNZ1CJUeqdoys...'
        }],
        'kind': 'sj#radioStation',
        'name': 'Heavy Metal Workout',
        'seed':
        {
            'curatedStationId': 'Lcwg73w3bd64hsrgarnorif52r',
            'kind': 'sj#radioSeed',
            'seedType': '9'
        },
        'skipEventHistory': [],
        'stationSeeds': [
        {
            'curatedStationId': 'Lcwg73w3bd64hsrgarnorif52r',
            'kind': 'sj#radioSeed',
            'seedType': '9'
        }]
    },
    'type': '6'
}],
'video_hits': [
{
    'score': 629.6226806640625,
    'type': '8',
    'youtube_video':
    {
        'id': '6PN78PS_QsM',
        'kind': 'sj#video',
        'thumbnails': [
        {
            'height': 180,
            'url': 'https://i.ytimg.com/vi/6PN78PS_QsM/mqdefault.jpg',
            'width': 320
        }],
        'title': 'J. Cole - Work Out'
    }
}]

}

经过 3 周的不同尝试,已清理但损坏的代码:(我尝试过 for、foreach、while,但它读取的最远可能是整个 unicode 数组、错误或空字符串)

sub search {
    my $query = shift;

    my $uri = 'googlemusic:search:' . $query;

    if (my $result = $cache->get($uri)) {
        return $result;
    }

    my $googleResult;
    my $result = {
        tracks => [],
        albums => [],
        artists => [],
    };        

    eval {
        $googleResult = $googleapi->search($query, $prefs->get('max_search_items'));
    };
    if ($@) {
        $log->error("Not able to search All Access for \"$query\": $@");
        return;
    }
    #gives not an ARRAY refernce error
    for my $hit (@{$googleResult->{song_hits}}) {
        push @{$result->{tracks}}, to_slim_track($hit->{track});
    }
    #works, but gives an error on the next line, 'newlist' object has no attribute 'album' 
    for my $hit ({$googleResult->{album_hits}}) {
        push @{$result->{albums}}, album_to_slim_album($hit->{album});
    }
    #Perl and others recommended way, but gives  Can't locate object method "FIRSTKEY" via package "Inline::Python::Object::Data"
    for my $hit (%{$googleResult->{artist_hits}}) {
        push @{$result->{artists}}, artist_to_slim_artist($hit->{artist});
    }

    # Add to the cache
    $cache->set($uri, $result, $CACHE_TIME);

    return $result;
}

我试过阅读,但遇到了很多错误,包括:

我的完整测试文件:http://pastebin.com/DMnDc56i GoogleApi PM(Python GAPI 挂钩):https://raw.githubusercontent.com/hechtus/squeezebox-googlemusic/master/GoogleMusic/GoogleAPI.pm

编辑:信息,有几个人想要修复未维护的旧代码,所以我愿意提供帮助并使除这部分之外的一切正常。

旧代码 Git:https://github.com/hechtus/squeezebox-googlemusic

Google Api Python 我用:https://github.com/simon-weber/gmusicapi

好的,这不是真正的答案,但出于好意,我为您清理了您的数据。这是一个真正的 Python 字典。我不知道一些数字字符串值是否应该是整数,所以我没有弄乱它们。如何处理截断的 URL 将由您决定。

解决此问题的另一种方法是将 True 更改为 true,将 False 更改为 false,并将字典解析为 JSON。

{
    'album_hits': [
    {
        'album':
        {
            'albumArtRef': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fF...',
            'albumArtist': 'J.Cole',
            'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y',
            'artist': 'J.Cole',
            'artistId': ['Ajgnxme45wcqqv44vykrleifpji'],
            'description_attribution':
            {
                'kind': 'sj#attribution',
                'license_title': 'Creative Commons Attribution CC-BY',
                'license_url': 'http://creativecommons.org/licenses/by/4.0/legalcode',
                'source_title': 'Freebase',
                'source_url': ''
            },
            'explicitType': '1',
            'kind': 'sj#album',
            'name': 'Work Out',
            'year': 2011
        },
        'type': '3'
    }],
    'artist_hits': [
    {
        'artist':
        {
            'artistArtRef': 'http://lh3.googleusercontent.com/MJe-cDw9uQ-pUagoLlm...',
            'artistArtRefs': [
            {
                'aspectRatio': '2',
                'autogen': False,
                'kind': 'sj#imageRef',
                'url': 'http://lh3.googleusercontent.com/MJe-cDw9uQ-pUagoLlmKX3x_K...'
            }],
            'artistId': 'Ajgnxme45wcqqv44vykrleifpji',
            'artist_bio_attribution':
            {
                'kind': 'sj#attribution',
                'source_title': 'David Jeffries, Rovi'
            },
            'kind': 'sj#artist',
            'name': 'J. Cole'
        },
        'type': '2'
    }],
    'playlist_hits': [
    {
        'playlist':
        {
            'albumArtRef': [
            {
                'url': 'http://lh3.googleusercontent.com/KJsAhrg8Jk_5A4xYLA68LFC...'
            }],
            'description': 'Workout Plan ',
            'kind': 'sj#playlist',
            'name': 'Workout',
            'ownerName': 'Ida Sarver',
            'shareToken': 'AMaBXyktyF6Yy_G-8wQy8Rru0tkueIbIFblt2h0BpkvTzHDz-fFj6P...',
            'type': 'SHARED'
        },
        'type': '4'
    }],
    'situation_hits': [
    {
        'situation':
        {
            'description': 'Level up and enter beast mode with some loud, aggressive music.',
            'id': 'Nrklpcyfewwrmodvtds5qlfp5ve',
            'imageUrl': 'http://lh3.googleusercontent.com/Cd8WRMaG_pDwjTC_dSPIIuf...',
            'title': 'Entering Beast Mode',
            'wideImageUrl': 'http://lh3.googleusercontent.com/8A9S-nTb5pfJLcpS8P...'
        },
        'type': '7'
    }],
    'song_hits': [
    {
        'track':
        {
            'album': 'Work Out',
            'albumArtRef': [
            {
                'aspectRatio': '1',
                'autogen': False,
                'kind': 'sj#imageRef',
                'url': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fFdxj5w...'
            }],
            'albumArtist': 'J.Cole',
            'albumAvailableForPurchase': True,
            'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y',
            'artist': 'J Cole',
            'artistId': ['Ajgnxme45wcqqv44vykrleifpji', 'Ampniqsqcwxk7btbgh5ycujij5i'],
            'composer': '',
            'discNumber': 1,
            'durationMillis': '234000',
            'estimatedSize': '9368582',
            'explicitType': '1',
            'genre': 'Pop',
            'kind': 'sj#track',
            'nid': 'Tq3nsmzeumhilpegkimjcnbr6aq',
            'primaryVideo':
            {
                'id': '6PN78PS_QsM',
                'kind': 'sj#video',
                'thumbnails': [
                {
                    'height': 180,
                    'url': 'https://i.ytimg.com/vi/6PN78PS_QsM/mqdefault.jpg',
                    'width': 320
                }]
            },
            'storeId': 'Tq3nsmzeumhilpegkimjcnbr6aq',
            'title': 'Work Out',
            'trackAvailableForPurchase': True,
            'trackAvailableForSubscription': True,
            'trackNumber': 1,
            'trackType': '7',
            'year': 2011
        },
        'type': '1'
    }],
    'station_hits': [
    {
        'station':
        {
            'compositeArtRefs': [
            {
                'aspectRatio': '1',
                'kind': 'sj#imageRef',
                'url': 'http://lh3.googleusercontent.com/3aD9mFppy6PwjADnjwv_w...'
            }],
            'contentTypes': ['1'],
            'description': 'These riff-tastic metal tracks are perfect for getting the blood pumping.',
            'imageUrls': [
            {
                'aspectRatio': '1',
                'autogen': False,
                'kind': 'sj#imageRef',
                'url': 'http://lh5.ggpht.com/YNGkFdrtk43e8H941fuAHjflrNZ1CJUeqdoys...'
            }],
            'kind': 'sj#radioStation',
            'name': 'Heavy Metal Workout',
            'seed':
            {
                'curatedStationId': 'Lcwg73w3bd64hsrgarnorif52r',
                'kind': 'sj#radioSeed',
                'seedType': '9'
            },
            'skipEventHistory': [],
            'stationSeeds': [
            {
                'curatedStationId': 'Lcwg73w3bd64hsrgarnorif52r',
                'kind': 'sj#radioSeed',
                'seedType': '9'
            }]
        },
        'type': '6'
    }],
    'video_hits': [
    {
        'score': 629.6226806640625,
        'type': '8',
        'youtube_video':
        {
            'id': '6PN78PS_QsM',
            'kind': 'sj#video',
            'thumbnails': [
            {
                'height': 180,
                'url': 'https://i.ytimg.com/vi/6PN78PS_QsM/mqdefault.jpg',
                'width': 320
            }],
            'title': 'J. Cole - Work Out'
        }
    }]
}

我认为显示的数据结构在$googleResult中。这是 'almost' JSON,您可以在简单清理后使用模块处理它。我将使用 JSON::XS。下面的代码在 $googleResult 被获取后运行。 (在测试中,我实际上将问题中显示的数据复制到文件中并读入。)我首先将 ' 替换为 " 和小写 TrueFalse,获得模块可以解码的有效 JSON 格式。

# Other code from the question ...
use JSON::XS;

# For tests I loaded shown data into $googleResult (did not run this eval)
eval {
    $googleResult = $googleapi->search($query, $prefs->get('max_search_items'));
};
if ($@) {
    $log->error("Not able to search All Access for \"$query\": $@");
    return;
}

# The structure shown in the question needs a cleanup
# But this may be a road to madness, if there is more
$googleResult =~ s/'/"/g;        # ' turn off wrong editor coloring
$googleResult =~ s/False/false/g;
$googleResult =~ s/True/true/g;

my $coder = JSON::XS->new;    
# There are many options for how to set it up. Example:
# JSON::XS->new->ascii->pretty->allow_nonref;    

my $data = $coder->decode($googleResult);  
# Now this is a normal Perl data structure that we can work with. 
# Look at what's under 'album_hits' for example
my $ralbhits = $data->{'album_hits'};  
print Dumper($ralbhits);
# We get: VAR1 = [ { 'album' => { albumID => ... } } ]
# Array reference, with nested hash references as the sole element

# Extract the 'artist'
my $artist = $ralbhits->[0]->{'album'}->{'artist'};
print "$artist\n";

这会打印 J. Cole(在我在这里省略的转储之后)。为了方便起见,您可以先提取结构的一部分,然后更简单地查询它。例如

# Get the hashref for album
my $ralbum = $ralbhits->[0]->{'album'};
my $artist = $ralbum->{'artist'};

现在,一旦数据被解压,您就可以根据 artist_to_slim_artist() 的需要和所做的,检索您需要的内容。这是一个正常的数据结构。

用于 JSON 解析 return Perl 数据结构的模块,请参阅 Mapping in JSON::XS. Generally they will be nested, except in very simple cases. For how to work with them see perldsc,一本关于复杂数据结构的食谱。


本例中给出的 JSON 对象虽然无效,但几乎不需要更正。然而,它可能会变得更加复杂。例如,有一个更大的文档 (~100kB) linked 在评论中,有这些问题。

  • 名称-值对包含在 ' 而不是 " 中,值本身包含 '(像 isn't 和其他缩写),使 ' 对的匹配复杂化。

  • 名称和值开头的 u' 序列无效(u 需要删除)。这可以与上述内容一起滚动,因为它们放在一起。还有u".

  • 文本可能包含各种转义,例如一些重音编码,这些都是无效的 JSON。 (该文档中的一个。)可以找到并修复(例如转义)。

花了几分钟想出一些正则表达式来更正 link 处的文档,大小接近 100kB,因此它可以用上面的代码干净地解析。但问题是很难说下一个文档中可能还有什么麻烦。尽管如此,由于这可能是正则表达式,所以这里可能很有趣。

名称和值不是包含在一对 " 中,而是在 ' 之间,并且领先的还有一个额外的字符 u'。使它更容易的是结束 ' 必须跟在 , : ] } 中的任何一个之后,我使用积极的前瞻来断言。最后,有一些 u" 开头引号和 u 被删除,首先。

$googleResult =~ s/False/false/g;
$googleResult =~ s/True/true/g;
$googleResult =~ s/u"/"/g;
# There are also escaped characters in text, escape that backslash
$googleResult =~ s|(\)||g;
# Correct delimiters from u'...' to "...", see text below   
$googleResult =~ s/u'(.*?)' (?= []:},] )/""/gx;
# We are good now, decode it
my $data = $coder->decode($googleResult); 
my $alb = $data->[0]{track}{album};
print "$alb\n";

这会打印 These Things Happen(正确)。上面我们在 u' 和第一个 ' 之间捕获,然后是 ]:,} 中的任何一个(为此使用字符 class [...])。然后 u''"" 代替。在这个 decode($googleResult); 工作之后,我们得到要查询的 Perl 数据结构。

有多种模块允许 'relaxed' 方法,并且会接受许多此类违规行为。但是,通过使用它们,我们同意使用无效的 JSON,这意味着一种简单明了的数据格式,我不建议走这条路。请注意,几乎完整的格式规范非常适合上面 link 中清晰且插图丰富的页面。另请参阅 JSON Example,了解一些示例。

我认为最好的办法是尝试清理它。 运行 像上面的代码一样的解码器并查看错误消息。它会准确地指出问题所在。然后添加一个正则表达式来纠正特定的格式违规。然后再去。如果您可能处理的各种文档或多或少存在相同的问题(例如上面的问题),它可能会很好地工作。或者,如果不断出现新的违规行为,可能会发现这太麻烦了,在这种情况下,您可能需要采用不同的方法。

最后,我不知道你是怎么从原来的Python-object 问题得出这个格式的。可能是格式在翻译的某个地方被破坏了吗?我不明白会怎样。它实际上不是JSON吗?但是,它离它太近了。

是否可以要求提供有效的JSON?

我已经使用列表理解制定了一个解决方案,如下所示:

use Inline::Python qw(py_eval);

my $song_hits = py_eval("[x for x in $googleResult->{song_hits}]", 0);
for my $hit (@$song_hits) {
    push @{$result->{tracks}}, to_slim_track($hit->{track});
}

提交位于: https://github.com/squeezebox-googlemusic/squeezebox-googlemusic/commit/e6fa62d9da3bc7295023283ef5d25698737e5772