Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
17 changes: 17 additions & 0 deletions lib_json/src/main/x/json/annotations/JsonProperty.x
Comment thread
thegridman marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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)
into Class | Property {
}
21 changes: 19 additions & 2 deletions lib_json/src/main/x/json/mappings/ReflectionMapping.x
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import annotations.JsonProperty;

/**
* A reflection-based [Mapping] implementation that works for exactly one type, `Serializable`.
*/
Expand Down Expand Up @@ -129,15 +131,30 @@ const ReflectionMapping<Serializable, StructType extends Struct>(
val structType = clazz.StructType;

PropertyMapping<structType.DataType>[] fields = new PropertyMapping[];
Set<String> names = new HashSet();
for (Property<structType.DataType> 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<prop.Referent> 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<structType.DataType, prop.Referent>
(prop.name, valueMapping, prop);
(name, valueMapping, prop);
}
}
}
Expand Down
34 changes: 34 additions & 0 deletions lib_json/src/test/x/JsonTest/annotations/JsonPropertyTest.x
Original file line number Diff line number Diff line change
@@ -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<Data> 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"}
;
}
}
Loading