Skip to content

Commit e98253e

Browse files
committed
Revised the JSOn format
1 parent f429a4a commit e98253e

3 files changed

Lines changed: 145 additions & 9 deletions

File tree

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(event["attributes"]["relatedTag"], [],
169+
"relatedTag should be omitted entirely, not present as empty list")
170+
171+
# Check that tags without certain attributes don't have empty lists
172+
item = tags.get("Item", {})
173+
if "relatedTag" in item.get("attributes", {}):
174+
self.assertNotEqual(item["attributes"]["relatedTag"], [],
175+
"Empty relatedTag should be omitted, not present as []")
165176

166177

167178
class TestJSONBackwardsCompatibility(unittest.TestCase):

tests/schema/test_schema_format_roundtrip.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,120 @@ 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(len(tags_with_empty_lists), 0,
285+
f"Found {len(tags_with_empty_lists)} empty list attributes: {tags_with_empty_lists[:5]}")
286+
287+
# Verify that tags WITH these attributes have non-empty lists
288+
if "Sensory-event" in json_data.get("tags", {}):
289+
sensory_attrs = json_data["tags"]["Sensory-event"].get("attributes", {})
290+
if "suggestedTag" in sensory_attrs:
291+
self.assertTrue(len(sensory_attrs["suggestedTag"]) > 0,
292+
"Sensory-event suggestedTag should be non-empty if present")
293+
294+
def test_extras_sections_roundtrip(self):
295+
"""Test that extras sections (Sources, Prefixes, AnnotationPropertyExternal) roundtrip correctly."""
296+
schema = load_schema_version("8.4.0")
297+
298+
# Check that original has extras
299+
orig_extras = getattr(schema, 'extras', {}) or {}
300+
self.assertGreater(len(orig_extras), 0, "Schema should have extras sections")
301+
302+
# Save and reload
303+
json_path = os.path.join(self.temp_dir, "with_extras.json")
304+
schema.save_as_json(json_path)
305+
reloaded = load_schema(json_path)
306+
307+
# Check reloaded has extras
308+
reloaded_extras = getattr(reloaded, 'extras', {}) or {}
309+
310+
# Compare each extras section
311+
self.assertEqual(set(orig_extras.keys()), set(reloaded_extras.keys()),
312+
"Extras sections should match")
313+
314+
for key in orig_extras.keys():
315+
orig_df = orig_extras[key]
316+
reloaded_df = reloaded_extras[key]
317+
self.assertTrue(orig_df.equals(reloaded_df),
318+
f"Extras section '{key}' should match after roundtrip")
319+
320+
def test_library_schema_extras_roundtrip(self):
321+
"""Test that library schema extras (external annotations, etc.) roundtrip correctly."""
322+
schema = load_schema_version("score_2.1.0")
323+
324+
# Check that library schema has extras
325+
orig_extras = getattr(schema, 'extras', {}) or {}
326+
self.assertGreater(len(orig_extras), 0, "Library schema should have extras sections")
327+
328+
# Check for external annotations specifically
329+
self.assertIn("AnnotationPropertyExternal", orig_extras,
330+
"Library schema should have external annotations")
331+
332+
# Save and reload
333+
json_path = os.path.join(self.temp_dir, "library_with_extras.json")
334+
schema.save_as_json(json_path, save_merged=False)
335+
reloaded = load_schema(json_path)
336+
337+
# Check reloaded has all extras
338+
reloaded_extras = getattr(reloaded, 'extras', {}) or {}
339+
self.assertEqual(set(orig_extras.keys()), set(reloaded_extras.keys()),
340+
"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),
347+
f"Library schema extras '{key}' should match after roundtrip")
348+
349+
def test_library_schema_score(self):
350+
"""Test score library schema roundtrip specifically."""
351+
schema = load_schema_version("score_2.1.0")
352+
353+
# Test unmerged format
354+
json_path = os.path.join(self.temp_dir, "score_unmerged.json")
355+
schema.save_as_json(json_path, save_merged=False)
356+
reloaded = load_schema(json_path)
357+
358+
# Verify library attributes
359+
self.assertEqual(schema.library, reloaded.library)
360+
self.assertEqual(schema.version, reloaded.version)
361+
self.assertEqual(schema.with_standard, reloaded.with_standard)
362+
363+
# Verify tag counts match
364+
self.assertEqual(len(schema.tags.all_entries), len(reloaded.tags.all_entries))
365+
366+
# Verify prologue and epilogue
367+
self.assertEqual(schema.prologue, reloaded.prologue)
368+
self.assertEqual(schema.epilogue, reloaded.epilogue)
369+
256370

257371
if __name__ == "__main__":
258372
unittest.main()

0 commit comments

Comments
 (0)