package com.openxc.messages.formatters;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.math.BigDecimal;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.openxc.messages.CanMessage;
import com.openxc.messages.Command;
import com.openxc.messages.CommandResponse;
import com.openxc.messages.DiagnosticResponse;
import com.openxc.messages.EventedSimpleVehicleMessage;
import com.openxc.messages.NamedVehicleMessage;
import com.openxc.messages.SimpleVehicleMessage;
import com.openxc.messages.UnrecognizedMessageTypeException;
import com.openxc.messages.VehicleMessage;
/**
* A formatter for serializing and deserializing JSON OpenXC messages.
*/
public class JsonFormatter {
private static Gson sGson = new Gson();
static {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
builder.registerTypeAdapter(byte[].class, new ByteAdapter());
builder.registerTypeAdapter(Double.class, new JsonSerializer<Double>() {
@Override
public JsonElement serialize(final Double src, final Type typeOfSrc, final JsonSerializationContext context) {
BigDecimal value = BigDecimal.valueOf(src);
return new JsonPrimitive(value);
}
});
sGson = builder.create();
}
/**
* Serialize a vehicle message to a string.
*
* @param message the message to serialize
* @return the message serialized to a String.
*/
public static String serialize(VehicleMessage message) {
return sGson.toJson(message);
}
/**
* Serialize a collection of messages to a string.
*
* Each message will be serialized to a JSON object, and they will all be
* included in a JSON array in the result.
*
* @param messages the messages to serialize.
* @return the messages serialized individually and in a JSON array.
*/
public static String serialize(Collection<VehicleMessage> messages) {
return sGson.toJson(messages);
}
/**
* Deserialize a single vehicle messages from the string.
*
* @param data a String containing the JSON serialized vehicle message.
* @throws UnrecognizedMessageTypeException if no message could be
* deserialized.
* @return the deserialized VehicleMessage.
*/
public static VehicleMessage deserialize(String data)
throws UnrecognizedMessageTypeException {
JsonObject root;
try {
JsonParser parser = new JsonParser();
root = parser.parse(data).getAsJsonObject();
} catch(JsonSyntaxException | IllegalStateException e) {
throw new UnrecognizedMessageTypeException(
"Unable to parse JSON from \"" + data + "\": " + e);
}
Set<String> fields = new HashSet<>();
for(Map.Entry<String, JsonElement> entry : root.entrySet()) {
fields.add(entry.getKey());
}
VehicleMessage message;
if(CanMessage.containsRequiredFields(fields)) {
message = sGson.fromJson(root, CanMessage.class);
} else if(DiagnosticResponse.containsRequiredFields(fields)) {
message = sGson.fromJson(root, DiagnosticResponse.class);
} else if(Command.containsRequiredFields(fields)) {
message = sGson.fromJson(root, Command.class);
} else if(CommandResponse.containsRequiredFields(fields)) {
message = sGson.fromJson(root, CommandResponse.class);
} else if(EventedSimpleVehicleMessage.containsRequiredFields(fields)) {
message = sGson.fromJson(root, EventedSimpleVehicleMessage.class);
} else if(SimpleVehicleMessage.containsRequiredFields(fields)) {
message = sGson.fromJson(root, SimpleVehicleMessage.class);
} else if(NamedVehicleMessage.containsRequiredFields(fields)) {
message = sGson.fromJson(root, NamedVehicleMessage.class);
} else if(fields.contains(VehicleMessage.EXTRAS_KEY)) {
message = sGson.fromJson(root, VehicleMessage.class);
} else {
throw new UnrecognizedMessageTypeException(
"Unrecognized combination of fields: " + fields);
}
return message;
}
}