在 YAML 文档中将 Python 嵌入对象作为原生 JSON

Emit Python embedded object as native JSON in YAML document

我正在从 Excel 导入网络服务测试并将它们序列化为 YAML。

但利用 YAML 作为 JSON 的超集,我希望测试的请求部分有效 JSON,即具有分隔符、引号和逗号。

这将允许我们在自动测试套件和手动测试工具(例如 Postman)之间剪切和粘贴请求。

所以这是我希望测试的样子(简化):

- properties:
    METHOD: GET
    TYPE: ADDRESS
    Request URL: /addresses
    testCaseId: TC2
  request:
    {
        "unitTypeCode": "",
        "unitNumber": "15",
        "levelTypeCode": "L",
        "roadNumber1": "810",
        "roadName": "HAY",
        "roadTypeCode": "ST",
        "localityName": "PERTH",
        "postcode": "6000",
        "stateTerritoryCode": "WA"
    }

在 Python 中,我的请求对象有一个名为 fieldsdict 属性,它是要序列化为 JSON 的对象的一部分。这是我试过的:

import yaml

def request_presenter(dumper, request):
    json_string = json.dumps(request.fields, indent=8)
    return dumper.represent_str(json_string)

yaml.add_representer(Request, request_presenter)

test = Test(...including embedded request object)
serialised_test = yaml.dump(test)

我得到:

- properties:
    METHOD: GET
    TYPE: ADDRESS
    Request URL: /addresses
    testCaseId: TC2
  request: "{
    \"unitTypeCode\": \"\",\n
    \"unitNumber\": \"15\",\n
    \"levelTypeCode": \"L\",\n
    \"roadNumber1\": \"810\",\n
    \"roadName\": \"HAY\",\n
    \"roadTypeCode\": \"ST\",\n
    \"localityName\": \"PERTH\",\n
    \"postcode\": \"6000\",\n
    \"stateTerritoryCode\": \"WA\"\n
  }"

...更糟的是因为它全部在一条线上并且到处都是白色 space。

我尝试对文字多行字符串使用 | 样式,这有助于换行和转义引号(涉及更多,但 this answer 很有帮助。)但是,转义或多行,结果仍然是一个需要单独解析的字符串。

如何停止 PyYaml 将 JSON 块作为字符串进行分析,并使其仅接受一段文本作为发出的 YAML 的一部分?我猜这与覆盖发射器有关,但我需要一些帮助。如果可能的话,我想避免post-处理序列化测试来实现这一点。

好的,这就是我想出的解决方案。提前生成带有地标的 YAML。 placemarker 标记了 JSON 应该插入的位置,并且还定义了 JSON 块的 root-level 缩进。

import os
import itertools
import json


def insert_json_in_yaml(pre_insert_yaml, key, obj_to_serialise):
    marker = '%s: null' % key
    marker_line = line_of_first_occurrence(pre_insert_yaml, marker)
    marker_indent = string_indent(marker_line)
    serialised = json.dumps(obj_to_serialise, indent=marker_indent + 4)
    key_with_json = '%s: %s' % (key, serialised)
    serialised_with_json = pre_insert_yaml.replace(marker, key_with_json)
    return serialised_with_json


def line_of_first_occurrence(basestring, substring):
    """
    return line number of first occurrence of substring
    """
    lineno = lineno_of_first_occurrence(basestring, substring)
    return basestring.split(os.linesep)[lineno]


def string_indent(s):
    """
    return indentation of a string (no of spaces before a nonspace)
    """
    spaces = ''.join(itertools.takewhile(lambda c: c == ' ', s))
    return len(spaces)


def lineno_of_first_occurrence(basestring, substring):
    """
    return line number of first occurrence of substring
    """
    return basestring[:basestring.index(substring)].count(os.linesep)


embedded_object = {
    "unitTypeCode": "",
    "unitNumber": "15",
    "levelTypeCode": "L",
    "roadNumber1": "810",
    "roadName": "HAY",
    "roadTypeCode": "ST",
    "localityName": "PERTH",
    "postcode": "6000",
    "stateTerritoryCode": "WA"
}
yaml_string = """
---

- properties:
    METHOD: GET
    TYPE: ADDRESS
    Request URL: /addresses
    testCaseId: TC2
  request: null
  after_request: another value
"""

>>> print(insert_json_in_yaml(yaml_string, 'request', embedded_object))
- properties:
    METHOD: GET
    TYPE: ADDRESS
    Request URL: /addresses
    testCaseId: TC2
  request: {
    "unitTypeCode": "",
    "unitNumber": "15",
    "levelTypeCode": "L",
    "roadNumber1": "810",
    "roadName": "HAY",
    "roadTypeCode": "ST",
    "localityName": "PERTH",
    "postcode": "6000",
    "stateTerritoryCode": "WA"
  }
  after_request: another value