From 2220772ee3a7a44d88f0469a7c18cbff535c948a Mon Sep 17 00:00:00 2001 From: Oleksandr Bazarnov Date: Fri, 1 Oct 2021 13:48:06 +0300 Subject: [PATCH 1/6] fixed targettingCriteria transformation, changed values of creatives/variables to string by default, bumped the version --- .../137ece28-5434-455c-8f34-69dc3782f451.json | 2 +- .../resources/seed/source_definitions.yaml | 2 +- .../connectors/source-linkedin-ads/Dockerfile | 2 +- .../connectors/source-linkedin-ads/setup.py | 2 +- .../schemas/creatives.json | 19 +---- .../source_linkedin_ads/utils.py | 72 +++++++++++-------- .../samples/test_data_for_tranform.py | 26 ++++++- 7 files changed, 72 insertions(+), 53 deletions(-) diff --git a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/137ece28-5434-455c-8f34-69dc3782f451.json b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/137ece28-5434-455c-8f34-69dc3782f451.json index 92b84c2b5aad..d6b3a6beab53 100644 --- a/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/137ece28-5434-455c-8f34-69dc3782f451.json +++ b/airbyte-config/init/src/main/resources/config/STANDARD_SOURCE_DEFINITION/137ece28-5434-455c-8f34-69dc3782f451.json @@ -2,6 +2,6 @@ "sourceDefinitionId": "137ece28-5434-455c-8f34-69dc3782f451", "name": "LinkedIn Ads", "dockerRepository": "airbyte/source-linkedin-ads", - "dockerImageTag": "0.1.0", + "dockerImageTag": "0.1.1", "documentationUrl": "https://docs.airbyte.io/integrations/sources/linkedin-ads" } diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 6d14342bcd49..4a25df7e638c 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -538,7 +538,7 @@ - sourceDefinitionId: 137ece28-5434-455c-8f34-69dc3782f451 name: LinkedIn Ads dockerRepository: airbyte/source-linkedin-ads - dockerImageTag: 0.1.0 + dockerImageTag: 0.1.1 documentationUrl: https://docs.airbyte.io/integrations/sources/linkedin-ads sourceType: api - sourceDefinitionId: b2e713cd-cc36-4c0a-b5bd-b47cb8a0561e diff --git a/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile b/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile index bddce18a5155..44dba64fc4ae 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile @@ -27,5 +27,5 @@ ENV TZ "UTC" ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.version=0.1.1 LABEL io.airbyte.name=airbyte/source-linkedin-ads diff --git a/airbyte-integrations/connectors/source-linkedin-ads/setup.py b/airbyte-integrations/connectors/source-linkedin-ads/setup.py index 231a23688bf7..1ae2b388c3fa 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/setup.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk==0.1.22", + "airbyte-cdk", "pendulum", ] diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/schemas/creatives.json b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/schemas/creatives.json index 62559878dd8d..4b150ad2a33d 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/schemas/creatives.json +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/schemas/creatives.json @@ -50,24 +50,7 @@ "type": ["null", "string"] }, "value": { - "anyOf": [ - { - "type": ["null", "string"] - }, - { - "type": ["null", "boolean"] - }, - { - "type": ["null", "number"] - }, - { - "type": ["null", "integer"] - }, - { - "type": ["null", "object"], - "additionalProperties": true - } - ] + "type": ["null", "string"] } } } diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py index 368a183ed8bf..9b00ff37f183 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py @@ -205,42 +205,57 @@ def transform_targeting_criteria( } """ + + def unnest_dict(nested_dict: Dict) -> Iterable[List]: + """ + Unnest the nested dict to simplify the normalization + + EXAMPLE OUTPUT: + [ + {"type": "some_key", "values": "some_values"}, + ..., + {"type": "some_other_key", "values": "some_othre_values"} + ] + """ + + result = [] + for key, value in nested_dict.items(): + values = [] + if isinstance(value, List): + if isinstance(value[0], str): + values = value + elif isinstance(value[0], Dict): + for v in value: + values.append(v) + elif isinstance(value, Dict): + values.append(value) + result.append({"type": key, "values": values}) + yield from result + + + # get the target dict from record targeting_criteria = record.get(dict_key) + # transform `include` if "include" in targeting_criteria: and_list = targeting_criteria.get("include").get("and") - for id, and_criteria in enumerate(and_list): - or_dict = and_criteria.get("or") - for key, value in or_dict.items(): - values = [] - if isinstance(value, list): - if isinstance(value[0], str): - values = value - elif isinstance(value[0], dict): - for v in value: - values.append(v) - elif isinstance(key, dict): - values.append(key) - # Replace the 'or' with {type:value} - record["targetingCriteria"]["include"]["and"][id]["type"] = key - record["targetingCriteria"]["include"]["and"][id]["values"] = values - record["targetingCriteria"]["include"]["and"][id].pop("or") + updated_include = {"and": []} + for k in and_list: + or_dict = k.get("or") + unnested = unnest_dict(or_dict) + for j in unnested: + updated_include["and"].append(j) + # Replace the original 'and' with updated_include + record["targetingCriteria"]["include"] = updated_include # transform `exclude` if present if "exclude" in targeting_criteria: or_dict = targeting_criteria.get("exclude").get("or") updated_exclude = {"or": []} - for key, value in or_dict.items(): - values = [] - if isinstance(value, list): - if isinstance(value[0], str): - values = value - elif isinstance(value[0], dict): - for v in value: - value.append(v) - elif isinstance(value, dict): - value.append(value) - updated_exclude["or"].append({"type": key, "values": values}) + unnested_dict = unnest_dict(or_dict) + for k in unnested_dict: + updated_exclude["or"].append(k) + # Replace the original 'or' with updated_exclude record["targetingCriteria"]["exclude"] = updated_exclude return record @@ -283,7 +298,8 @@ def transform_variables( record["variables"]["type"] = key record["variables"]["values"] = [] for key, param in params.items(): - record["variables"]["values"].append({"key": key, "value": param}) + value = str(param) # convert various datatypes of values into the string + record["variables"]["values"].append({"key": key, "value": value}) # Clean the nested structure record["variables"].pop("data") return record diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py index 07512a62f807..3dcd415694ed 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py @@ -21,8 +21,20 @@ ] } }, - {"or": {"urn:li:adTargetingFacet:locations": ["urn:li:geo:103644278"]}}, - {"or": {"urn:li:adTargetingFacet:interfaceLocales": ["urn:li:locale:en_US"]}}, + { + "or": { + "urn:li:adTargetingFacet:locations": [ + "urn:li:geo:103644278" + ] + } + }, + { + "or": { + "urn:li:adTargetingFacet:interfaceLocales": [ + "urn:li:locale:en_US" + ] + } + }, ] }, "exclude": { @@ -52,6 +64,10 @@ "activity": "urn:li:activity:1234", "directSponsoredContent": 0, "share": "urn:li:share:1234", + "custom_num_var": 1234, + "custom_obj_var": {'key': 1234}, + "custom_arr_var": [1, 2, 3, 4], + "custom_null_var": None, } } }, @@ -103,8 +119,12 @@ "type": "com.linkedin.ads.SponsoredUpdateCreativeVariables", "values": [ {"key": "activity", "value": "urn:li:activity:1234"}, - {"key": "directSponsoredContent", "value": 0}, + {"key": "directSponsoredContent", "value": "0"}, {"key": "share", "value": "urn:li:share:1234"}, + {"key": "custom_num_var", "value": "1234"}, + {"key": "custom_obj_var", "value": "{'key': 1234}"}, + {"key": "custom_arr_var", "value": "[1, 2, 3, 4]"}, + {"key": "custom_null_var", "value": "None"}, ], }, "created": "2021-08-21 21:27:55", From befa6cb357c4e67372835850140281b1855a847e Mon Sep 17 00:00:00 2001 From: Oleksandr Bazarnov Date: Fri, 1 Oct 2021 14:10:02 +0300 Subject: [PATCH 2/6] fixed PEP issue for Dockerfile --- .../connectors/source-linkedin-ads/Dockerfile | 12 +++++++++--- .../source_linkedin_ads/utils.py | 5 ++--- .../samples/test_data_for_tranform.py | 18 +++--------------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile b/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile index 44dba64fc4ae..31a00a200351 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile @@ -5,7 +5,9 @@ FROM base as builder WORKDIR /airbyte/integration_code # upgrade pip to the latest version -RUN apk --no-cache upgrade && pip install --upgrade pip +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base COPY setup.py ./ # install necessary packages to a temporary folder @@ -17,13 +19,17 @@ WORKDIR /airbyte/integration_code # copy all loaded and built libraries to a pure basic image COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash # copy payload code only COPY main.py ./ COPY source_linkedin_ads ./source_linkedin_ads -# set the default Timezone, for use with dependent libraries like: datetime, pendullum, etc. -ENV TZ "UTC" ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py index 9b00ff37f183..dea2ff3bf357 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py @@ -209,7 +209,7 @@ def transform_targeting_criteria( def unnest_dict(nested_dict: Dict) -> Iterable[List]: """ Unnest the nested dict to simplify the normalization - + EXAMPLE OUTPUT: [ {"type": "some_key", "values": "some_values"}, @@ -231,7 +231,6 @@ def unnest_dict(nested_dict: Dict) -> Iterable[List]: values.append(value) result.append({"type": key, "values": values}) yield from result - # get the target dict from record targeting_criteria = record.get(dict_key) @@ -298,7 +297,7 @@ def transform_variables( record["variables"]["type"] = key record["variables"]["values"] = [] for key, param in params.items(): - value = str(param) # convert various datatypes of values into the string + value = str(param) # convert various datatypes of values into the string record["variables"]["values"].append({"key": key, "value": value}) # Clean the nested structure record["variables"].pop("data") diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py index 3dcd415694ed..9c7284c4843e 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py @@ -21,20 +21,8 @@ ] } }, - { - "or": { - "urn:li:adTargetingFacet:locations": [ - "urn:li:geo:103644278" - ] - } - }, - { - "or": { - "urn:li:adTargetingFacet:interfaceLocales": [ - "urn:li:locale:en_US" - ] - } - }, + {"or": {"urn:li:adTargetingFacet:locations": ["urn:li:geo:103644278"]}}, + {"or": {"urn:li:adTargetingFacet:interfaceLocales": ["urn:li:locale:en_US"]}}, ] }, "exclude": { @@ -65,7 +53,7 @@ "directSponsoredContent": 0, "share": "urn:li:share:1234", "custom_num_var": 1234, - "custom_obj_var": {'key': 1234}, + "custom_obj_var": {"key": 1234}, "custom_arr_var": [1, 2, 3, 4], "custom_null_var": None, } From c91733977b87ec75e94470313d71a50aa85030b8 Mon Sep 17 00:00:00 2001 From: Oleksandr Bazarnov Date: Fri, 1 Oct 2021 14:17:02 +0300 Subject: [PATCH 3/6] added changelog --- docs/integrations/sources/linkedin-ads.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/integrations/sources/linkedin-ads.md b/docs/integrations/sources/linkedin-ads.md index c3917658c586..902b2e991cd6 100644 --- a/docs/integrations/sources/linkedin-ads.md +++ b/docs/integrations/sources/linkedin-ads.md @@ -132,4 +132,5 @@ The complete set of prmissions is: | Version | Date | Pull Request | Subject | | :------ | :-------- | :-------- | :------ | +| 0.1.1 | 2021-10-02 | [6610](https://github.com/airbytehq/airbyte/pull/6610) | Fix for `Campaigns/targetingCriteria` transformation, coerced `Creatives/variables/values` to string by default | | 0.1.0 | 2021-09-05 | [5285](https://github.com/airbytehq/airbyte/pull/5285) | Initial release of Native LinkedIn Ads connector for Airbyte | From 692110a0ac930a773550472e33ff184c7f50f528 Mon Sep 17 00:00:00 2001 From: Oleksandr Bazarnov Date: Mon, 4 Oct 2021 18:02:39 +0300 Subject: [PATCH 4/6] updated after review --- .../source_linkedin_ads/utils.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py index dea2ff3bf357..ebccf6962a06 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py @@ -5,6 +5,7 @@ from typing import Any, Dict, Iterable, List, Mapping import pendulum as pdm +import json def get_parent_stream_values(record: Dict, key_value_map: Dict) -> Dict: @@ -206,7 +207,7 @@ def transform_targeting_criteria( """ - def unnest_dict(nested_dict: Dict) -> Iterable[List]: + def unnest_dict(nested_dict: Dict) -> Iterable[Dict]: """ Unnest the nested dict to simplify the normalization @@ -218,7 +219,6 @@ def unnest_dict(nested_dict: Dict) -> Iterable[List]: ] """ - result = [] for key, value in nested_dict.items(): values = [] if isinstance(value, List): @@ -229,8 +229,7 @@ def unnest_dict(nested_dict: Dict) -> Iterable[List]: values.append(v) elif isinstance(value, Dict): values.append(value) - result.append({"type": key, "values": values}) - yield from result + yield {"type": key, "values": values} # get the target dict from record targeting_criteria = record.get(dict_key) @@ -241,8 +240,7 @@ def unnest_dict(nested_dict: Dict) -> Iterable[List]: updated_include = {"and": []} for k in and_list: or_dict = k.get("or") - unnested = unnest_dict(or_dict) - for j in unnested: + for j in unnest_dict(or_dict): updated_include["and"].append(j) # Replace the original 'and' with updated_include record["targetingCriteria"]["include"] = updated_include @@ -251,8 +249,7 @@ def unnest_dict(nested_dict: Dict) -> Iterable[List]: if "exclude" in targeting_criteria: or_dict = targeting_criteria.get("exclude").get("or") updated_exclude = {"or": []} - unnested_dict = unnest_dict(or_dict) - for k in unnested_dict: + for k in unnest_dict(or_dict): updated_exclude["or"].append(k) # Replace the original 'or' with updated_exclude record["targetingCriteria"]["exclude"] = updated_exclude @@ -296,9 +293,9 @@ def transform_variables( for key, params in variables.items(): record["variables"]["type"] = key record["variables"]["values"] = [] - for key, param in params.items(): - value = str(param) # convert various datatypes of values into the string - record["variables"]["values"].append({"key": key, "value": value}) + for key, value in params.items(): + # convert various datatypes of values into the string + record["variables"]["values"].append({"key": key, "value": json.dumps(value)}) # Clean the nested structure record["variables"].pop("data") return record From 5e55f79468217a1c204182724811177edfe07adf Mon Sep 17 00:00:00 2001 From: Oleksandr Bazarnov Date: Tue, 5 Oct 2021 10:54:33 +0300 Subject: [PATCH 5/6] ... --- .../source-linkedin-ads/source_linkedin_ads/utils.py | 4 ++-- .../utils_tests/samples/test_data_for_tranform.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py index ebccf6962a06..ac2ff5ef5f93 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py @@ -2,10 +2,10 @@ # Copyright (c) 2021 Airbyte, Inc., all rights reserved. # +import json from typing import Any, Dict, Iterable, List, Mapping import pendulum as pdm -import json def get_parent_stream_values(record: Dict, key_value_map: Dict) -> Dict: @@ -295,7 +295,7 @@ def transform_variables( record["variables"]["values"] = [] for key, value in params.items(): # convert various datatypes of values into the string - record["variables"]["values"].append({"key": key, "value": json.dumps(value)}) + record["variables"]["values"].append({"key": key, "value": json.dumps(value, ensure_ascii=True)}) # Clean the nested structure record["variables"].pop("data") return record diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py index 9c7284c4843e..87585ee8fcb4 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py @@ -106,13 +106,13 @@ "variables": { "type": "com.linkedin.ads.SponsoredUpdateCreativeVariables", "values": [ - {"key": "activity", "value": "urn:li:activity:1234"}, + {"key": "activity", "value": '"urn:li:activity:1234"'}, {"key": "directSponsoredContent", "value": "0"}, - {"key": "share", "value": "urn:li:share:1234"}, + {"key": "share", "value": '"urn:li:share:1234"'}, {"key": "custom_num_var", "value": "1234"}, - {"key": "custom_obj_var", "value": "{'key': 1234}"}, + {"key": "custom_obj_var", "value": '{"key": 1234}'}, {"key": "custom_arr_var", "value": "[1, 2, 3, 4]"}, - {"key": "custom_null_var", "value": "None"}, + {"key": "custom_null_var", "value": "null"}, ], }, "created": "2021-08-21 21:27:55", From 79140a1cb618465788b1cf7a071b40391bd0268c Mon Sep 17 00:00:00 2001 From: Oleksandr Bazarnov Date: Wed, 6 Oct 2021 11:45:21 +0300 Subject: [PATCH 6/6] updated after review --- .../source_linkedin_ads/utils.py | 13 +++++++------ .../utils_tests/samples/test_data_for_tranform.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py index ac2ff5ef5f93..7f4161c83235 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py @@ -215,18 +215,19 @@ def unnest_dict(nested_dict: Dict) -> Iterable[Dict]: [ {"type": "some_key", "values": "some_values"}, ..., - {"type": "some_other_key", "values": "some_othre_values"} + {"type": "some_other_key", "values": "some_other_values"} ] """ for key, value in nested_dict.items(): values = [] if isinstance(value, List): - if isinstance(value[0], str): - values = value - elif isinstance(value[0], Dict): - for v in value: - values.append(v) + if len(value) > 0: + if isinstance(value[0], str): + values = value + elif isinstance(value[0], Dict): + for v in value: + values.append(v) elif isinstance(value, Dict): values.append(value) yield {"type": key, "values": values} diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py index 87585ee8fcb4..66cce8e60ea8 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py @@ -23,6 +23,8 @@ }, {"or": {"urn:li:adTargetingFacet:locations": ["urn:li:geo:103644278"]}}, {"or": {"urn:li:adTargetingFacet:interfaceLocales": ["urn:li:locale:en_US"]}}, + {"or": {"empty_dict_with_empty_list": []}}, # dict is present, but list is empty + {"or": {}}, # empty dict ] }, "exclude": { @@ -35,6 +37,7 @@ "facet_test3", "facet_test4", ], + "empty_list": [] } }, }, @@ -88,6 +91,10 @@ "type": "urn:li:adTargetingFacet:interfaceLocales", "values": ["urn:li:locale:en_US"], }, + { + "type": "empty_dict_with_empty_list", + "values": [], + }, ] }, "exclude": { @@ -100,6 +107,10 @@ "type": "urn:li:adTargetingFacet:facet_Key2", "values": ["facet_test3", "facet_test4"], }, + { + "type": "empty_list", + "values": [], + }, ] }, },