Skip to content

Commit 5063fc5

Browse files
authored
Updated the schema JSON format, schema comparison and tests
Updated the schema JSON format, schema comparison and tests
2 parents f429a4a + 450c112 commit 5063fc5

File tree

3 files changed

+144
-9
lines changed

3 files changed

+144
-9
lines changed

hed/schema/schema_io/schema2json.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -399,11 +399,22 @@ def build_attributes_dict(source_dict, include_takes_value):
399399
if value:
400400
attrs["takesValue"] = value
401401

402-
# List attributes - always include (even if empty)
403-
attrs["suggestedTag"] = get_list_value(HedKey.SuggestedTag, source_dict)
404-
attrs["relatedTag"] = get_list_value(HedKey.RelatedTag, source_dict)
405-
attrs["valueClass"] = get_list_value(HedKey.ValueClass, source_dict)
406-
attrs["unitClass"] = get_list_value(HedKey.UnitClass, source_dict)
402+
# List attributes - only include if non-empty
403+
suggested_tag = get_list_value(HedKey.SuggestedTag, source_dict)
404+
if suggested_tag:
405+
attrs["suggestedTag"] = suggested_tag
406+
407+
related_tag = get_list_value(HedKey.RelatedTag, source_dict)
408+
if related_tag:
409+
attrs["relatedTag"] = related_tag
410+
411+
value_class = get_list_value(HedKey.ValueClass, source_dict)
412+
if value_class:
413+
attrs["valueClass"] = value_class
414+
415+
unit_class = get_list_value(HedKey.UnitClass, source_dict)
416+
if unit_class:
417+
attrs["unitClass"] = unit_class
407418

408419
# Single value attributes
409420
default_units = source_dict.get(HedKey.DefaultUnits)

tests/schema/test_json_explicit_attributes.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,25 @@ def test_language_item_inherits_suggested_tag(self):
154154
self.assertIn("Sensory-presentation", lang["explicitAttributes"]["suggestedTag"])
155155

156156
def test_empty_lists_omitted(self):
157-
"""Test that empty lists are represented as empty arrays, not omitted."""
157+
"""Test that empty lists are omitted from JSON output (not written as empty arrays)."""
158158
json_data = self._get_json_output()
159159
tags = json_data["tags"]
160160

161-
# Most tags should have relatedTag as empty list
161+
# Tags without relatedTag/valueClass/unitClass should not have these keys at all
162162
event = tags["Event"]
163-
self.assertIn("relatedTag", event["attributes"])
164-
self.assertEqual(event["attributes"]["relatedTag"], [])
163+
# Event has suggestedTag, so it should be present
164+
self.assertIn("suggestedTag", event["attributes"])
165+
# But if Event doesn't have relatedTag, it should be omitted entirely
166+
if "relatedTag" in event["attributes"]:
167+
# If present, it must be non-empty
168+
self.assertNotEqual(
169+
event["attributes"]["relatedTag"], [], "relatedTag should be omitted entirely, not present as empty list"
170+
)
171+
172+
# Check that tags without certain attributes don't have empty lists
173+
item = tags.get("Item", {})
174+
if "relatedTag" in item.get("attributes", {}):
175+
self.assertNotEqual(item["attributes"]["relatedTag"], [], "Empty relatedTag should be omitted, not present as []")
165176

166177

167178
class TestJSONBackwardsCompatibility(unittest.TestCase):

tests/schema/test_schema_format_roundtrip.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,119 @@ def test_library_schema_header_attributes(self):
253253
# Version number might be different for unmerged (without library prefix)
254254
self.assertEqual(schema.with_standard, schema_unmerged.with_standard)
255255

256+
def test_json_empty_list_attributes_omitted(self):
257+
"""Test that empty list attributes (suggestedTag, relatedTag, etc.) are omitted from JSON."""
258+
import json
259+
260+
schema = load_schema_version("8.4.0")
261+
json_path = os.path.join(self.temp_dir, "empty_lists.json")
262+
schema.save_as_json(json_path)
263+
264+
# Read the JSON file and check for empty list attributes
265+
with open(json_path, "r", encoding="utf-8") as f:
266+
json_data = json.load(f)
267+
268+
# Check ALL tags for empty lists
269+
tags_with_empty_lists = []
270+
271+
for tag_name, tag_data in json_data.get("tags", {}).items():
272+
# Check attributes dict
273+
attrs = tag_data.get("attributes", {})
274+
for list_attr in ["suggestedTag", "relatedTag", "valueClass", "unitClass"]:
275+
if list_attr in attrs and attrs[list_attr] == []:
276+
tags_with_empty_lists.append(f"{tag_name}.attributes.{list_attr}")
277+
278+
# Check explicitAttributes dict
279+
explicit_attrs = tag_data.get("explicitAttributes", {})
280+
for list_attr in ["suggestedTag", "relatedTag", "valueClass", "unitClass"]:
281+
if list_attr in explicit_attrs and explicit_attrs[list_attr] == []:
282+
tags_with_empty_lists.append(f"{tag_name}.explicitAttributes.{list_attr}")
283+
284+
self.assertEqual(
285+
len(tags_with_empty_lists),
286+
0,
287+
f"Found {len(tags_with_empty_lists)} empty list attributes: {tags_with_empty_lists[:5]}",
288+
)
289+
290+
# Verify that tags WITH these attributes have non-empty lists
291+
if "Sensory-event" in json_data.get("tags", {}):
292+
sensory_attrs = json_data["tags"]["Sensory-event"].get("attributes", {})
293+
if "suggestedTag" in sensory_attrs:
294+
self.assertTrue(
295+
len(sensory_attrs["suggestedTag"]) > 0, "Sensory-event suggestedTag should be non-empty if present"
296+
)
297+
298+
def test_extras_sections_roundtrip(self):
299+
"""Test that extras sections (Sources, Prefixes, AnnotationPropertyExternal) roundtrip correctly."""
300+
schema = load_schema_version("8.4.0")
301+
302+
# Check that original has extras
303+
orig_extras = getattr(schema, "extras", {}) or {}
304+
self.assertGreater(len(orig_extras), 0, "Schema should have extras sections")
305+
306+
# Save and reload
307+
json_path = os.path.join(self.temp_dir, "with_extras.json")
308+
schema.save_as_json(json_path)
309+
reloaded = load_schema(json_path)
310+
311+
# Check reloaded has extras
312+
reloaded_extras = getattr(reloaded, "extras", {}) or {}
313+
314+
# Compare each extras section
315+
self.assertEqual(set(orig_extras.keys()), set(reloaded_extras.keys()), "Extras sections should match")
316+
317+
for key in orig_extras.keys():
318+
orig_df = orig_extras[key]
319+
reloaded_df = reloaded_extras[key]
320+
self.assertTrue(orig_df.equals(reloaded_df), f"Extras section '{key}' should match after roundtrip")
321+
322+
def test_library_schema_extras_roundtrip(self):
323+
"""Test that library schema extras (external annotations, etc.) roundtrip correctly."""
324+
schema = load_schema_version("score_2.1.0")
325+
326+
# Check that library schema has extras
327+
orig_extras = getattr(schema, "extras", {}) or {}
328+
self.assertGreater(len(orig_extras), 0, "Library schema should have extras sections")
329+
330+
# Check for external annotations specifically
331+
self.assertIn("AnnotationPropertyExternal", orig_extras, "Library schema should have external annotations")
332+
333+
# Save and reload
334+
json_path = os.path.join(self.temp_dir, "library_with_extras.json")
335+
schema.save_as_json(json_path, save_merged=False)
336+
reloaded = load_schema(json_path)
337+
338+
# Check reloaded has all extras
339+
reloaded_extras = getattr(reloaded, "extras", {}) or {}
340+
self.assertEqual(set(orig_extras.keys()), set(reloaded_extras.keys()), "Library schema extras sections should match")
341+
342+
# Verify each extras dataframe matches
343+
for key in orig_extras.keys():
344+
orig_df = orig_extras[key]
345+
reloaded_df = reloaded_extras[key]
346+
self.assertTrue(orig_df.equals(reloaded_df), f"Library schema extras '{key}' should match after roundtrip")
347+
348+
def test_library_schema_score(self):
349+
"""Test score library schema roundtrip specifically."""
350+
schema = load_schema_version("score_2.1.0")
351+
352+
# Test unmerged format
353+
json_path = os.path.join(self.temp_dir, "score_unmerged.json")
354+
schema.save_as_json(json_path, save_merged=False)
355+
reloaded = load_schema(json_path)
356+
357+
# Verify library attributes
358+
self.assertEqual(schema.library, reloaded.library)
359+
self.assertEqual(schema.version, reloaded.version)
360+
self.assertEqual(schema.with_standard, reloaded.with_standard)
361+
362+
# Verify tag counts match
363+
self.assertEqual(len(schema.tags.all_entries), len(reloaded.tags.all_entries))
364+
365+
# Verify prologue and epilogue
366+
self.assertEqual(schema.prologue, reloaded.prologue)
367+
self.assertEqual(schema.epilogue, reloaded.epilogue)
368+
256369

257370
if __name__ == "__main__":
258371
unittest.main()

0 commit comments

Comments
 (0)