From e48a770a875e48d02c8f63321358e8f6a7755171 Mon Sep 17 00:00:00 2001 From: Jonathan Knight Date: Thu, 30 Apr 2026 17:02:39 +0300 Subject: [PATCH 1/2] Allow custom names for properties in JSON serialization --- .../main/x/json/annotations/JsonProperty.x | 8 +++++ .../main/x/json/mappings/ReflectionMapping.x | 21 ++++++++++-- .../x/JsonTest/annotations/JsonPropertyTest.x | 34 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 lib_json/src/main/x/json/annotations/JsonProperty.x create mode 100644 lib_json/src/test/x/JsonTest/annotations/JsonPropertyTest.x diff --git a/lib_json/src/main/x/json/annotations/JsonProperty.x b/lib_json/src/main/x/json/annotations/JsonProperty.x new file mode 100644 index 0000000000..93a6ff3861 --- /dev/null +++ b/lib_json/src/main/x/json/annotations/JsonProperty.x @@ -0,0 +1,8 @@ +/** + * An annotation to allow customization of the JSON name of a value. + * + * @param jsonName - the customized name of the field + */ +annotation JsonProperty(String jsonName) + into Class | Property { +} diff --git a/lib_json/src/main/x/json/mappings/ReflectionMapping.x b/lib_json/src/main/x/json/mappings/ReflectionMapping.x index 7840003083..de0b5bd4f0 100644 --- a/lib_json/src/main/x/json/mappings/ReflectionMapping.x +++ b/lib_json/src/main/x/json/mappings/ReflectionMapping.x @@ -1,3 +1,5 @@ +import annotations.JsonProperty; + /** * A reflection-based [Mapping] implementation that works for exactly one type, `Serializable`. */ @@ -129,15 +131,30 @@ const ReflectionMapping( val structType = clazz.StructType; PropertyMapping[] fields = new PropertyMapping[]; + Set names = new HashSet(); for (Property prop : structType.properties) { if (prop.hasField && !prop.lazy) { assert !prop.isConstant() && !prop.abstract && !prop.injected; // TODO CP what if the referent type is the same as "type"? (linked list example) if (Mapping valueMapping := schema.findMapping(prop.Referent)) { - // TODO CP - name has to be unique + String name = prop.name; + if (prop.is(JsonProperty)) { + name = prop.jsonName; + } + if (names.contains(name)) { + for (PropertyMapping mapping : fields) { + if (mapping.name == name) { + throw new IllegalJSON($|Duplicate JSON property name {name} \ + | {prop} name matches {mapping.property} + ); + } + } + } + names.add(name); + fields += new PropertyMapping - (prop.name, valueMapping, prop); + (name, valueMapping, prop); } } } diff --git a/lib_json/src/test/x/JsonTest/annotations/JsonPropertyTest.x b/lib_json/src/test/x/JsonTest/annotations/JsonPropertyTest.x new file mode 100644 index 0000000000..a1b1b90f52 --- /dev/null +++ b/lib_json/src/test/x/JsonTest/annotations/JsonPropertyTest.x @@ -0,0 +1,34 @@ +import json.Mapping; +import json.ObjectInputStream; +import json.Parser; +import json.Schema; + +import json.annotations.JsonProperty; + +class JsonPropertyTest { + + Schema jsonSchema = Schema.DEFAULT; + + static const Data(@JsonProperty("$data") String? s, String foo) {} + + @Test + void shouldParseValueWithCustomPropertyName() { + Data data = new Data("One", "Two"); + String jsonStr = \|{"$data":"One", "foo":"Two"} + ; + Mapping valueMapping = jsonSchema.ensureMapping(Data); + using (ObjectInputStream stream = new ObjectInputStream(jsonSchema, jsonStr.toReader())) { + val parsed = valueMapping.read(stream.ensureElementInput()); + assert parsed == data; + } + } + + @Test + public void shouldWriteValueWithCustomPropertyName() { + StringBuffer buf = new StringBuffer(); + Data data = new Data("One", "Two"); + jsonSchema.createObjectOutput(buf).write(data); + assert buf.toString() == \|{"$data":"One","foo":"Two"} + ; + } +} From 787d95b6e1403d6e108a768a9cf0556ad47c9d5d Mon Sep 17 00:00:00 2001 From: Jonathan Knight Date: Thu, 30 Apr 2026 17:19:05 +0300 Subject: [PATCH 2/2] Update JsonProperty.x doc --- lib_json/src/main/x/json/annotations/JsonProperty.x | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib_json/src/main/x/json/annotations/JsonProperty.x b/lib_json/src/main/x/json/annotations/JsonProperty.x index 93a6ff3861..6e76087372 100644 --- a/lib_json/src/main/x/json/annotations/JsonProperty.x +++ b/lib_json/src/main/x/json/annotations/JsonProperty.x @@ -1,6 +1,15 @@ /** * An annotation to allow customization of the JSON name of a value. * + * For example, if the JSON field name for a property needs to be "$data", this is invalid as an + * Ecstasy identifier name. The property can be annotated like this: + * + * @JsonProperty("$data") + * String data; + * + * The [ReflectionMapping] will use the value from the annotation to map the JSON field name to the + * property name. + * * @param jsonName - the customized name of the field */ annotation JsonProperty(String jsonName)