Skip to content

Commit

Permalink
Add MapMapping for json serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
Gene Gleyzer committed Jun 21, 2022
1 parent 5b366af commit 716ff7b
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 23 deletions.
36 changes: 35 additions & 1 deletion lib_ecstasy/src/main/x/ecstasy/reflect/Signature.x
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,40 @@ interface Signature<ParamTypes extends Tuple<ParamTypes>, ReturnTypes extends Tu
*/
@RO Parameter[] params;

/**
* @return the number of parameters with default values
*/
@RO Int defaultParamCount.get()
{
Int count = 0;
for (Parameter param : params)
{
if (param.defaultValue())
{
count++;
}
}
return count;
}

/**
* @return the number of required parameters (without default values)
*/
@RO Int requiredParamCount.get()
{
Int count = 0;
for (Parameter param : params)
{
if (param.defaultValue())
{
// there are no regular parameters after the first parameter with a default value
break;
}
count++;
}
return count;
}

/**
* Find a parameter by the provided name.
*
Expand Down Expand Up @@ -196,4 +230,4 @@ interface Signature<ParamTypes extends Tuple<ParamTypes>, ReturnTypes extends Tu

return buf.add(')');
}
}
}
4 changes: 2 additions & 2 deletions lib_ecstasy/src/main/x/ecstasy/reflect/Type.x
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ interface Type<DataType, OuterType>
assert:arg outer != Null;
for (val fn : constructors)
{
if (fn.ParamTypes.size == 1)
if (fn.requiredParamCount == 1)
{
assert fn.ParamTypes[0] == OuterType.DataType;
return True, fn.as(function DataType(OuterType)).
Expand All @@ -647,7 +647,7 @@ interface Type<DataType, OuterType>
{
for (val fn : constructors)
{
if (fn.ParamTypes.size == 0)
if (fn.requiredParamCount == 0)
{
return True, fn.as(function DataType());
}
Expand Down
106 changes: 106 additions & 0 deletions lib_json/src/main/x/json/mappings/MapMapping.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* A mapping for an immutable Map object.
*
* TODO GG: if Key is String and Value is Doc we could optimize the json representation as a
* regular json object
*/
const MapMapping<Key, Value>
(Mapping<Key> keyMapping, Mapping<Value> valueMapping)
implements Mapping<Map<Key, Value>>
{
/**
* Construct the MapMapping.
*
* @param keyMapping the mapping to use for the keys of the `Map`
* @param valueMapping the mapping to use for the values of the `Map`
*/
construct(Mapping<Key> keyMapping, Mapping<Value> valueMapping)
{
this.keyMapping = keyMapping;
this.valueMapping = valueMapping;
this.typeName = $"Map<{keyMapping.typeName}, {valueMapping.typeName}>";
}

@Override
Serializable read(ElementInput in)
{
Schema schema = in.schema;

Map<Key, Value> map;
if (schema.enableMetadata,
Doc typeName ?= in.peekMetadata(schema.typeKey),
typeName.is(String))
{
assert val typeMap := schema.typeForName(typeName).is(Type<Map<Key, Value>>);
assert val constructor := typeMap.defaultConstructor();
map = constructor();
}
else
{
map = new ListMap();
}

using (FieldInput mapInput = in.openObject())
{
using (ElementInput entriesInput = mapInput.openArray("e"))
{
while (entriesInput.canRead)
{
using (FieldInput entryInput = entriesInput.openObject())
{
Key key;
Value value;

using (ElementInput keyInput = entryInput.openField("k"))
{
key = keyInput.readUsing(keyMapping);
}

using (ElementInput valueInput = entryInput.openField("v"))
{
value = valueInput.readUsing(valueMapping);
}
map.put(key, value);
}
}
}
}
return map.is(Freezable) ? map.freeze(inPlace=True) : map.makeImmutable();
}

@Override
void write(ElementOutput out, Serializable map)
{
Schema schema = out.schema;

using (FieldOutput mapOutput = out.openObject())
{
using (ElementOutput entriesOutput = mapOutput.openArray("e"))
{
for ((Key key, Value value) : map.as(Map<Key, Value>))
{
using (FieldOutput entryOutput = entriesOutput.openObject())
{
entryOutput.addUsing(keyMapping, "k", key);
entryOutput.addUsing(valueMapping, "v", value);
}
}
}
}
}

@Override
<SubType extends Map<Key, Value>> conditional Mapping<SubType> narrow(Schema schema, Type<SubType> type)
{
if (SubType.Key != Key, SubType.Value != Value,
val narrowedKey := schema.findMapping(SubType.Key),
val narrowedValue := schema.findMapping(SubType.Value),
&narrowedKey != &keyMapping, &narrowedValue != &valueMapping)
{
return True, new MapMapping<SubType.Key, SubType.Value>(narrowedKey, narrowedValue)
.as(Mapping<SubType>);
}

return False;
}
}
11 changes: 9 additions & 2 deletions lib_json/src/main/x/json/mappings/Narrowable.x
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ mixin Narrowable<Serializable>
Schema schema = out.schema;
if (schema.enableMetadata)
{
out.prepareMetadata(schema.typeKey, schema.nameForType(type));
// there is no reason to record the immutability modifier for the type;
// we only support immutable types and it makes it simpler for the reader
Type typeSansImmutable = type;
if (type.explicitlyImmutable)
{
assert typeSansImmutable := type.modifying();
}
out.prepareMetadata(schema.typeKey, schema.nameForType(typeSansImmutable));
}

if (Mapping<Serializable> substitute := narrow(schema, type), &substitute != &this)
Expand All @@ -60,4 +67,4 @@ mixin Narrowable<Serializable>

super(out, value);
}
}
}
17 changes: 15 additions & 2 deletions lib_json/src/main/x/json/mappings/ReflectionMapping.x
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,20 @@ const ReflectionMapping<Serializable, StructType extends Struct>(
return True, new ArrayMapping<elementType.DataType>(elementMapping).as(Mapping<SubType>);
}
return False;
}
}

if (type.is(Type<Map>))
{
assert Type keyType := type.resolveFormalType("Key");
assert Type valueType := type.resolveFormalType("Value");
if (val keyMapping := schema.findMapping(keyType.DataType),
val valueMapping := schema.findMapping(valueType.DataType))
{
return True, new @Narrowable MapMapping<keyType.DataType, valueType.DataType>
(keyMapping, valueMapping).as(Mapping<SubType>);
}
return False;
}

assert val clazz := type.fromClass();

Expand Down Expand Up @@ -186,4 +199,4 @@ const ReflectionMapping<Serializable, StructType extends Struct>(
Property<StructType, Value> property,
Boolean subclassable = True,
Value? defaultValue = Null);
}
}
44 changes: 28 additions & 16 deletions manualTests/src/main/x/TestSimple.x
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,37 @@ module TestSimple
{
@Inject Console console;

void run( )
package json import json.xtclang.org;

import ecstasy.io.*;
import json.*;

void run()
{
Boolean flag = True;

Map<Int, String> map;
if (String name := name(), flag)
{
TODO
}
else
{
map = new HashMap();
}

console.println(map); // use to fail to compile: Variable "map" is not definitely assigned
Schema schema = Schema.DEFAULT;

Map<Int, String> map = new HashMap();
map = map.put(1, "a");
map = map.put(2, "b");
testSer(schema, "map", new Test(map));

// HashMap<String, String> map = new HashMap();
// map = map.put(1, "a");
// map = map.put(2, "b");
// testSer(schema, "map", new Test(map));
}

conditional String name()
private <Ser> void testSer(Schema schema, String name, Ser val)
{
return False;
StringBuffer buf = new StringBuffer();
schema.createObjectOutput(buf).write(val);

String s = buf.toString();
console.println($"JSON {name} written out={s}");

Ser val2 = schema.createObjectInput(new CharArrayReader(s)).read<Ser>();
console.println($"read {name} back in={val2}");
}

const Test(Map<Int, String> map);
}

0 comments on commit 716ff7b

Please sign in to comment.