Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from ._diff_utils import KVComparer, print_preview
from ._utils import (
is_json_content_type,
strip_json_comments,
validate_feature_flag_name,
validate_feature_flag_key,
)
Expand Down Expand Up @@ -87,7 +88,7 @@ def __read_with_appropriate_encoding(file_path, format_):
try:
with io.open(file_path, "r", encoding=default_encoding) as config_file:
if format_ == "json":
config_data = json.load(config_file)
config_data = __parse_json_file_data(config_file.read())
# Only accept json objects
if not isinstance(config_data, (dict, list)):
raise ValueError(
Expand All @@ -112,7 +113,7 @@ def __read_with_appropriate_encoding(file_path, format_):

with io.open(file_path, "r", encoding=detected_encoding) as config_file:
if format_ == "json":
config_data = json.load(config_file)
config_data = __parse_json_file_data(config_file.read())

elif format_ == "yaml":
for yaml_data in list(yaml.safe_load_all(config_file)):
Expand Down Expand Up @@ -1121,3 +1122,11 @@ def __flatten_key_value(key, value, flattened_data, depth, separator):
) # Ensure boolean values are properly stringified.
else:
flattened_data[key] = value if isinstance(value, str) else json.dumps(value)


def __parse_json_file_data(json_string):
Comment thread
albertofori marked this conversation as resolved.
Outdated
try:
return json.loads(json_string)

except json.JSONDecodeError:
return json.loads(strip_json_comments(json_string))
77 changes: 77 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appconfig/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@

logger = get_logger(__name__)

DOUBLE_QUOTE = '\"'
BACKSLASH = '\\'
DOUBLE_SLASH = '//'
MULTILINE_COMMENT_START = '/*'
MULTILINE_COMMENT_END = '*/'
NEW_LINE = '\n'


def construct_connection_string(cmd, config_store_name):
connection_string_template = 'Endpoint={};Id={};Secret={}'
Expand Down Expand Up @@ -254,3 +261,73 @@ def parse_tags_to_dict(tags):
tags_dict[tag_key] = tag_value
return tags_dict
return tags


Comment thread
albertofori marked this conversation as resolved.
Outdated
def strip_json_comments(json_string):
Comment thread
albertofori marked this conversation as resolved.
Outdated
current_index = 0
length = len(json_string)
result = []

def __isEscaped(json_string, char_index):
backslash_count = 0
idx = char_index - 1
while idx >= 0 and json_string[idx] == '\\':
Comment thread
albertofori marked this conversation as resolved.
Outdated
backslash_count += 1
idx -= 1

return backslash_count % 2 == 1

def __find_next_newline_index(json_string, start_index):
Comment thread
albertofori marked this conversation as resolved.
Outdated
idx = start_index

while idx < length:
if json_string[idx] == NEW_LINE:
break
Comment thread
albertofori marked this conversation as resolved.
Outdated

idx += 1

return idx

def __find_next_double_quote_index(json_string, start_index):
Comment thread
albertofori marked this conversation as resolved.
Outdated
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These helper functions lack docstrings explaining their parameters, return values, and potential exceptions. Add comprehensive docstrings for better code documentation.

Copilot uses AI. Check for mistakes.
Comment thread
albertofori marked this conversation as resolved.
Outdated
idx = start_index
while idx < length:
if json_string[idx] == DOUBLE_QUOTE and not __isEscaped(json_string, idx):
return idx

idx += 1

raise ValueError("Unterminated string literal")

def __find_next_multiline_comment_end(json_string, start_index):
Comment thread
albertofori marked this conversation as resolved.
Outdated
idx = start_index
while idx < length - 1:
if json_string[idx:idx + 2] == MULTILINE_COMMENT_END:
return idx + 1

idx += 1

raise ValueError("Unterminated multi-line comment")
Comment thread
albertofori marked this conversation as resolved.
Outdated

while current_index < length:
# Single line comment
if json_string[current_index:current_index + 2] == DOUBLE_SLASH:
current_index = __find_next_newline_index(json_string, current_index)
if current_index < length and json_string[current_index] == NEW_LINE:
result.append(NEW_LINE)

# Multi-line comment
elif json_string[current_index:current_index + 2] == MULTILINE_COMMENT_START:
current_index = __find_next_multiline_comment_end(json_string, current_index + 2)

# String literal
elif json_string[current_index] == DOUBLE_QUOTE:
literal_start_index = current_index
current_index = __find_next_double_quote_index(json_string, current_index + 1)

result.extend(json_string[literal_start_index:current_index + 1])
else:
result.append(json_string[current_index])

current_index += 1

return "".join(result)
24 changes: 18 additions & 6 deletions src/azure-cli/azure/cli/command_modules/appconfig/keyvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
JsonDiff, ImportMode)
from ._featuremodels import map_keyvalue_to_featureflag
from ._models import (convert_configurationsetting_to_keyvalue, convert_keyvalue_to_configurationsetting)
from ._utils import get_appconfig_data_client, prep_filter_for_url_encoding, resolve_store_metadata, get_store_endpoint_from_connection_string, is_json_content_type
from ._utils import get_appconfig_data_client, prep_filter_for_url_encoding, resolve_store_metadata, get_store_endpoint_from_connection_string, is_json_content_type, strip_json_comments

from ._diff_utils import print_preview, KVComparer
from .feature import __list_features
Expand Down Expand Up @@ -507,9 +507,8 @@ def set_key(cmd,
if retrieved_kv is None:
if is_json_content_type(content_type):
try:
# Ensure that provided value is valid JSON. Error out if value is invalid JSON.
value = 'null' if value is None else value
json.loads(value)
# Ensure that provided value is valid JSON and strip comments if needed.
value = 'null' if value is None else __normalize_json_input(value)
except ValueError:
raise CLIErrors.ValidationError('Value "{}" is not a valid JSON object, which conflicts with the content type "{}".'.format(value, content_type))

Expand All @@ -523,8 +522,8 @@ def set_key(cmd,
content_type = retrieved_kv.content_type if content_type is None else content_type
if is_json_content_type(content_type):
try:
# Ensure that provided/existing value is valid JSON. Error out if value is invalid JSON.
json.loads(value)
# Ensure that provided value is valid JSON and strip comments if needed.
value = __normalize_json_input(value)
except (TypeError, ValueError):
raise CLIErrors.ValidationError('Value "{}" is not a valid JSON object, which conflicts with the content type "{}". Set the value again in valid JSON format.'.format(value, content_type))
set_kv = ConfigurationSetting(key=key,
Expand Down Expand Up @@ -984,3 +983,16 @@ def list_revision(cmd,
return retrieved_revisions
except HttpResponseError as ex:
raise CLIErrors.AzureResponseError('List revision operation failed.\n' + str(ex))


def __normalize_json_input(json_string):
try:
json.loads(json_string)

return json_string

except json.JSONDecodeError:
string_without_comments = strip_json_comments(json_string)
Comment thread
albertofori marked this conversation as resolved.
Outdated

json.loads(string_without_comments)
return string_without_comments
Loading