I’m working on splitting a SignalK JSON object into canonical JSON items representing each value.
The original JSON looks like this:
{
"mmsi": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"name": "Mona",
"navigation": {
"position": {
"timestamp": "1991-09-03T03:5:36.000Z",
"latitude": 51.763691,
"longitude": 9.501367,
"altitude": 0.000000,
"source": "N0183-01"
},
"courseOverGroundTrue": {
"value": 23.000000
},
"speedOverGround": {
"value": 2.010289
}
},
"environment": {
"depth": {
"belowTransducer": {
"value": 12.700000
}
},
"wind": {
"angleApparent": {
"value": 0.174533
},
"speedApparent": {
"value": 0.000000
}
}}}
The needed transformed JSON looks like this, with JSON elements representing each value, and with item naming representing the whole path of the value.
{
"items": [{
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "EnvironmentWindSpeedApparent",
"isStep": false,
"name": "EnvironmentWindSpeedApparent",
"timestamps": 1523962903470,
"type": "numerical",
"values": 0.0
},
"key": "20180417-130143470EnvironmentWindSpeedApparent5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "EnvironmentWindAngleApparent",
"isStep": false,
"name": "EnvironmentWindAngleApparent",
"timestamps": 1523962903470,
"type": "numerical",
"values": 0.174533
},
"key": "20180417-130143470EnvironmentWindAngleApparent5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "EnvironmentDepthBelowTransducer",
"isStep": false,
"name": "EnvironmentDepthBelowTransducer",
"timestamps": 1523962903470,
"type": "numerical",
"values": 12.7
},
"key": "20180417-130143470EnvironmentDepthBelowTransducer5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationPositionLongitude",
"isStep": false,
"name": "NavigationPositionLongitude",
"timestamps": 1523962903470,
"type": "numerical",
"values": 9.501367
},
"key": "20180417-130143470NavigationPositionLongitude5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationPositionLatitude",
"isStep": false,
"name": "NavigationPositionLatitude",
"timestamps": 1523962903470,
"type": "numerical",
"values": 51.763691
},
"key": "20180417-130143470NavigationPositionLatitude5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationCourseOverGroundTrue",
"isStep": false,
"name": "NavigationCourseOverGroundTrue",
"timestamps": 1523962903470,
"type": "numerical",
"values": 23.0
},
"key": "20180417-130143470NavigationCourseOverGroundTrue5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationSpeedOverGround",
"isStep": false,
"name": "NavigationSpeedOverGround",
"timestamps": 1523962903470,
"type": "numerical",
"values": 2.010289
},
"key": "20180417-130143470NavigationSpeedOverGround5377770-4ee4-4a4b-3230-888037332031"
}
]}
How to do this transformation in a flexible way that adopts to changing sub-nodes being available in the original JSON ? I’m transforming it now in a simplistic way, but would like to know if it can be done using JsonReader , gson or other ways of iterating through the original JSON object.
I ended up defining the object structure representing the transformed json, and writing a custom serializer to do the transformation. It could be more efficient ways of doing it, but this approach seems to be working OK.
public class SignalKDeserializer implements JsonDeserializer<TargetObject> {
//written based on examples from http://www.javacreed.com/gson-deserialiser-example/
final TargetObject targetObject = new TargetObject ();
@Override
public TargetObject deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
traverse (jsonObject,0,"", mmsi);
return targetObject;
}
private void traverse (JsonObject jsonObject, Integer level, String parentName, String mmsi) {
Set<String> keys = jsonObject.keySet();
Iterator<?> keysIterator = keys.iterator ();
while( keysIterator.hasNext ()) {
String key = (String) keysIterator.next ();
String signalName = parentName+upperCaseFirst (key); //setting signalName to complete path of value
if (jsonObject.get (key) instanceof JsonObject) {
traverse ((jsonObject.get (key)).getAsJsonObject (),level+1,upperCaseFirst (signalName),mmsi);
} else if (jsonObject.get (key) instanceof JsonElement) {
if (level>0) {
try {
final Double value = jsonObject.get(key).getAsDouble ();
calendar = Calendar.getInstance ();
Long timeStamp = calendar.getTimeInMillis ();
Item targetItem = new Item ();
targetItem.columns.setIsStep (false);
targetItem.columns.setAssetId (mmsi);
targetItem.columns.setTimestamps (calendar.getTimeInMillis ());
targetItem.columns.setType ("numerical");
targetItem.columns.setDescription (signalName);
targetItem.columns.setValues (value);
targetItem.columns.setName (signalName);
targetItem.setKey (signaldateformat.format (timeStamp) + mmsi);
targetObject.items.add (targetItem);
}
catch (NumberFormatException n) {
// Expected, the value is non numerical and will not be transformed
}
}
}
}
}}
I'm using the serializer from my main class like this:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(TargetObject.class, new SignalKDeserializer ());
final Gson gson = gsonBuilder.create();
TargetObject targetObject = gson.fromJson (jsonSignalK,TargetObject.class);
String jsonOutString = gson.toJson (targetObject);
jsonOutString contains the transformed json I needed.