Home > Software design >  Extract value from anomalous dictionary
Extract value from anomalous dictionary

Time:02-20

I have a set of strings formatted as BBB below, and I need to extract the value corresponding to the text key (in the example below it's "My life is amazing").

BBB = str({"id": "18976", "episode_done": False, "text": "My life is amazing", 
    "text_candidates": ["My life is amazing", "I am worried about global warming"], 
    "metrics": {"clen": AverageMetric(12), "ctrunc": AverageMetric(0), 
    "ctrunclen": AverageMetric(0)}})

I tried converting BBB into a string and then into a dictionary using json.load and ast.literal_eval, but I get error messages in both cases. I suppose this is due to the fact that the metrics key has a dictionary as a value.

How do you suggest to solve the issue? Thanks.

CodePudding user response:

Having a dictionary as a value is not the problem, that's just called nested dictionaries, which is perfectly fine.

I'm not sure what your initial data (and its type) is, but here's a demo of using your dictionary. Supposing you have a dictionary

BBB_dict = {
    "id": "18976",
    "episode_done": False,
    "text": "My life is amazing", 
    "text_candidates": ["My life is amazing", "I am worried about global warming"], 
    "metrics": {
        "clen": AverageMetric(12),
        "ctrunc": AverageMetric(0), 
        "ctrunclen": AverageMetric(0)
    }
}

You have to note that calling str(BBB_dict) does not create a JSON string. (related). To convert such a dictionary to a JSON string, you could do something like:

BBB = json.dumps(BBB_dict)

But this would probably raise following exception for you:

TypeError: Object of type AverageMetric is not JSON serializable

Well, that's because Python does not know which attributes of your AverageMetric class to use when creating a JSON from it. So, you have to

def serialize(obj):
    if isinstance(obj, AverageMetric):
        return {
            'x': obj.x,
            'y': obj.y,
            'z': obj.z
        }
    
    return {}

This method specifies what fields to use when creating a JSON (i.e. serializing an AverageMetrics object). (related) so you could create your JSON string as follows:

BBB = json.dumps(BBB_dict, default=serialize)

Which would result in the following:

'{"id": "18976", "episode_done": false, "text": "My life is amazing", "text_candidates": ["My life is amazing", "I am worried about global warming"], "metrics": {"clen": {"x": 12, "y": 1, "z": "z"}, "ctrunc": {"x": 0, "y": 1, "z": "z"}, "ctrunclen": {"x": 0, "y": 1, "z": "z"}}}'

CodePudding user response:

You could adapt the source of ast.literal_eval() to something that parses function calls (and other non-literals), but into strings:

import ast

BBB = """
{"id": "18976", "episode_done": False, "text": "My life is amazing", 
    "text_candidates": ["My life is amazing", "I am worried about global warming"], 
    "metrics": {"clen": AverageMetric(12), "ctrunc": AverageMetric(0), 
    "ctrunclen": AverageMetric(0)}}
""".strip()


def literal_eval_with_function_calls(source):
    # Adapted from `ast.literal_eval`
    def _convert(node):
        if isinstance(node, list):
            return [_convert(arg) for arg in node]
        if isinstance(node, ast.Constant):
            return node.value
        if isinstance(node, ast.Tuple):
            return tuple(map(_convert, node.elts))
        if isinstance(node, ast.List):
            return list(map(_convert, node.elts))
        if isinstance(node, ast.Set):
            return set(map(_convert, node.elts))
        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'set' and node.args == node.keywords == []:
            return set()
        if isinstance(node, ast.Dict):
            return dict(zip(map(_convert, node.keys), map(_convert, node.values)))
        if isinstance(node, ast.Expression):
            return _convert(node.body)
        return {
            f'${node.__class__.__name__}': ast.get_source_segment(source, node),
        }

    return _convert(ast.parse(source, mode='eval'))


print(literal_eval_with_function_calls(BBB))

This outputs

{'episode_done': False,
 'id': '18976',
 'metrics': {'clen': {'$Call': 'AverageMetric(12)'},
             'ctrunc': {'$Call': 'AverageMetric(0)'},
             'ctrunclen': {'$Call': 'AverageMetric(0)'}},
 'text': 'My life is amazing',
 'text_candidates': ['My life is amazing', 'I am worried about global warming']}

However, it would be better to just have data that's not in a non-parseable format to begin with...

CodePudding user response:

You can use a regex:

import re

>>> re.findall('(?<="text": )"(.*)"', BBB)[0]
'My life is amazing'
  • Related