TransWikia.com

JSON上の辞書の値で一致するものがあるかどうか判別したいが、どのように現在のコードを修正すれば良いかわからない

スタック・オーバーフロー Asked on September 1, 2021

実現したいこと

以下のような2つのJSONファイルを用いて、menu.jsonを書き換えようとしています。

menu.jsonにおける名前とallergies.jsonにおける名前が一致(部分一致した場合)、allergies.jsonの「素材」の項目を、想定出力のようにmenu.jsonに書き加えたいです。

menu.json

{
    "メニュー":{
        "メインディッシュ":[
            {
                "名前":"ステーキ",
                "価格":1000
            },
            {
                "名前":"焼き魚",
                "価格":800
            }
        ],

        "サラダ":[
            {
                "名前":"マカロニサラダ",
                "価格":300
            },
            {
                "名前":"シーザーサラダ ハーフ",
                "価格":500
            }
        ]
    }
}

allergies.json

{
    "アレルギー":{
        "サラダ":[
            {
                "素材":{
                    "乳製品":"チーズ",
                    "ナッツ":"くるみ"
                },
                "商品名":{
                    "名前1":"シーザーサラダ",
                    "名前2":"フレンチサラダ"
                }
            }

        ]
    }
}

想定出力

{
    "メニュー":{
        "メインディッシュ":[
            {
                "名前":"ステーキ",
                "価格":1000
            },
            {
                "名前":"魚のソテー",
                "価格":800
            }
        ],

        "サラダ":[
            {
                "名前":"マカロニサラダ",
                "価格":300
            },
            {
                "名前":"シーザーサラダ ハーフ",
                "価格":500,
                "素材":{
                    "乳製品":"チーズ",
                    "ナッツ":"くるみ"
                }
            }
        ]
    }
}

発生している問題

以下まで現在できているのですが、allergies.jsonの"商品名"の値とmenu.jsonの名前が一致したら、「素材」の項目を取ってくると言う分岐をどのように書けばいいのかわからず困っています。

現在の出力では以下のようにしか表示されず、実現したいことができていない状態です。

            {
                "名前":"シーザーサラダ",
                "価格":500
            }

コード

jsonであるキーに対する値を取得し、値に応じて要素を追加したいという前の質問の回答を参考にさせていただいて、今回のコードを書きました。

import json

#ファイル読み出し
file_path1 = 'menu.json'
json_file1 = open(file_path1, 'r')
json_object1 = json.load(json_file1)

file_path2 = 'allergies.json'
json_file2 = open(file_path2, 'r')
json_object2 = json.load(json_file2)

menus = json_object1["メニュー"]["サラダ"]
allergies = json_object2["アレルギー"]["サラダ"]


def get_allegies(allergies, name):
    print(allergies[0]["名前1"])
    if allergies[0]["名前1"] == name:
        return allergies[0]["素材"]
    return None

def set_combination(menu, allergies, name_key, new_key):
    try:
        name = menu[name_key]
        allergies = get_allegies(allergies, name)
        menu[new_key] = allergies

    except KeyError:
        pass


for menu  in menus:
    set_combination(menu, allergies, "名前", "素材")

print(json.dumps(menu , indent=4))


試したこと

普通の文字列であれば以下のようにして、含まれているかどうか判別できることはわかっています
参考記事

print('bbb' in 'aaa-bbb-ccc')
# True

環境

python 3.7.4

2 Answers

入力する JSON ファイルを変更しない場合は以下の様になります。ここで、「allergies.json の"商品名"の値と menu.json の名前が一致したら、『素材』の項目を取得する処理」は products = ... 以降の部分になります。

import json
from copy import deepcopy

# menu
with open('menu.json', 'r') as f:
  menu = json.load(f)
  # new dict for update
  updated_menu = deepcopy(menu)
  if 'メニュー' in menu:
    menu = menu['メニュー']

# allergie
with open('allergies.json', 'r') as f:
  allergies = json.load(f)
  if 'アレルギー' in allergies:
    allergies = allergies['アレルギー']

# update
for ak, av in allergies.items():
  if ak not in menu: continue
  for i, contents in enumerate(av):
    if '商品名' not in contents: continue
    products = [
      contents['商品名'][name] for name in contents['商品名']
      if name.startswith('名前')
    ]
    if not products: continue
    for j, v in enumerate(menu[ak]):
      if '名前' not in v: continue
      if any([p in v['名前'] for p in products]):
        updated_menu['メニュー'][ak][j]['素材'] = av[i]['素材']

print(json.dumps(updated_menu, indent=4, ensure_ascii=False))

Correct answer by user39889 on September 1, 2021

まず、allergies.jsonデータの方を以下のように素材内容が同じでも別々のデータとして配列化します。

{
    "アレルギー":{
        "サラダ":[
            {
                "素材":{
                    "乳製品":"チーズ",
                    "ナッツ":"くるみ"
                },
                "商品名":{
                    "名前":"シーザーサラダ"
                }
            },
            {
                "素材":{
                    "乳製品":"チーズ",
                    "ナッツ":"くるみ"
                },
                "商品名":{
                    "名前":"フレンチサラダ"
                }
            }
        ]
    }
}

そしてプログラムの方は以下記事の応用でjsonへのアクセス方法を単純化出来るようにします。
PythonでJSONデータを扱う

objectっぽくアクセスしたい
例えば、dictを継承して属性が有るかのように振る舞うクラスを定義する方法があります。

class ObjectLike(dict):
    # __getattr__ は属性がなかった場合に実行される特殊メソッドで、dict.getを利用するようにする
    __getattr__ = dict.get

jsonパッケージと一緒に使う
この ObjectLike をJSONの読み取りの際にも利用します。
json.loads には object_hookという引数があり、JSONのObjectを処理する際のフックを仕込むことができるようになっています。dict を受け取って何らかの値を返す関数であれば良いので ObjectLike をそのまま使用することができます。

user = json.loads(json_data, object_hook=ObjectLike)

上記を応用して以下のように実装することが出来ます。
Windows環境でやったのでencoding関連の指定を増やしています。

import json

class ObjectLike(dict):  #### 追加
    __getattr__ = dict.get  #### 追加

file_path1 = 'menu.json'
json_file1 = open(file_path1, 'r', encoding='utf-8')
menus = json.load(json_file1, object_hook=ObjectLike)  #### 変更

file_path2 = 'allergies.json'
json_file2 = open(file_path2, 'r', encoding='utf-8')
allergies = json.load(json_file2, object_hook=ObjectLike)  #### 変更

#### アレルギー情報を主体にループする
for alrgcategory in allergies.アレルギー:
    for alrgitem in allergies.アレルギー[alrgcategory]:
        itemname = alrgitem.商品名.名前
        
        #### メニューに該当項目があるか検索し、あれば情報を追記する
        for i, item in enumerate(menus.メニュー[alrgcategory]):
            if itemname in item.名前:
                menus.メニュー[alrgcategory][i]['素材'] = alrgitem.素材

print(json.dumps(menus, indent=4, ensure_ascii=False))

Answered by kunif on September 1, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP