Now we are goint to write an interface for serializing generic data to different formats. This is not the only way, but gives a strong base to begin with. ## Serializable interface The main things you would want to support are the following: 1. Serialize any kind of data. 2. Support multiple output formats. 3. Do not make any unnecessary work. One way to do this is to define a `Serializable` interface and a class for each format you want to support: ``` interface Serializable { fn serialize(serializer: @'Serializer, obj: Self) -> String; } // A class for each format. These might have state class SerializerFormat1 {} class SerializerFormat2 {} class SerializerFormat3 {} [...] ``` After this, you could implement `Serializable` for each datat type that you want to be able to serialize: ``` fn serialize(serializer: @SerializerFormat1, obj: Int) -> String { [...] } implement Serializable for Int; ``` ## JSON Let's build an example for JSON using a class called `JSONSerializer`: ``` // Serializer class class JSONSerializer {} // Functions fn serialize(serializer: @JSONSerializer, obj: Int) -> String { return obj.deref().to_string(); } fn serialize(serializer: @JSONSerializer, obj: Float) -> String { return obj.deref().to_string(); } fn serialize(serializer: @JSONSerializer, obj: String) -> String { return "\"" + obj + "\""; } // Implementations implement Serializable for Int; implement Serializable for Float; implement Serializable for String; ``` Now you can make use of what you built to create `Array` serialization: ``` import * from range; fn serialize(serializer: @JSONSerializer, obj: Array<'T [Serializable]>) -> String { let res = "["; for i in range(0, obj.len()) { res = res + serializer.serialize(*obj[i]); if i < obj.len() - 1 { res = res + ", "; } } return res + "]"; } implement Serializable for Array<'T [Serializable]>; ``` This code allows you to use **bounded template substitution** to specify that an `Array` of `'T` is serializable as long as `'T` is also serializable. Note that We import the range standard library, this is not strictly needed. Now we only have dictionaries left. This is one way to support them by using custom classes: ``` // Class to serialize class Test { att_a: Int; att_b: Array; } // Fucntions fn serialize_map_field(serializer: @JSONSerializer, key: 'K [Serializable], value: 'V [Serializable]) -> String { return serializer.serialize(*key) + ": " + serializer.serialize(*value); } fn serialize(serializer: @JSONSerializer, obj: Test) -> String { let a = serializer.serialize_map_field("att_a", move(obj.att_a)); let b = serializer.serialize_map_field("att_b", move(obj.att_b)); return "{" + a + ", " + b + "}"; } // Implementation implement Serializable for Test; ``` After this you can serialize `Test` instances like this: ``` let serializer = JSONSerializer(); let a = arr(); a.push(5.2); a.push(8.76); a.push(15.9); let test = Test(10, move(a)); print(serializer.serialize(move(test))); // Prints {"att_a": 10, "att_b": [5.2, 8.76, 15.9]} ```