package org.opensky.model; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; /** * Custom JSON deserializer for OpenSkyStates retrieved from the API. * * XXX Because actual state vectors arrive as array we need a custom deserializer like this. * If anyone comes up with something better, feel free to create a pull request! * * @author Markus Fuchs, [email protected] */ public class OpenSkyStatesDeserializer extends StdDeserializer<OpenSkyStates> { public OpenSkyStatesDeserializer() { super(OpenSkyStates.class); } private Collection<StateVector> deserializeStates(JsonParser jp) throws IOException { ArrayList<StateVector> result = new ArrayList<>(); for (JsonToken next = jp.nextToken(); next != null && next != JsonToken.END_ARRAY; next = jp.nextToken()) { if (next == JsonToken.START_ARRAY) { continue; } if (next == JsonToken.END_OBJECT) { break; } String icao24 = jp.getText(); if ("null".equals(icao24)) { throw new JsonParseException("Got 'null' icao24", jp.getCurrentLocation()); } StateVector sv = new StateVector(icao24); sv.setCallsign(jp.nextTextValue()); sv.setOriginCountry(jp.nextTextValue()); sv.setLastPositionUpdate((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setLastContact((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setLongitude((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setLatitude((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setBaroAltitude((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setOnGround(jp.nextBooleanValue()); sv.setVelocity((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setHeading((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setVerticalRate((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); // sensor serials if present next = jp.nextToken(); if (next == JsonToken.START_ARRAY) { for (next = jp.nextToken(); next != null && next != JsonToken.END_ARRAY; next = jp.nextToken()) { sv.addSerial(jp.getIntValue()); } } sv.setGeoAltitude((jp.nextToken() != null && jp.getCurrentToken() != JsonToken.VALUE_NULL ? jp.getDoubleValue() : null)); sv.setSquawk(jp.nextTextValue()); sv.setSpi(jp.nextBooleanValue()); int psi = jp.nextIntValue(0); StateVector.PositionSource ps = psi <= StateVector.PositionSource.values().length ? StateVector.PositionSource.values()[psi] : StateVector.PositionSource.UNKNOWN; sv.setPositionSource(ps); // there are additional fields (upward compatibility), consume until end of this state vector array next = jp.nextToken(); while (next != null && next != JsonToken.END_ARRAY) { // ignore next = jp.nextToken(); } // consume "END_ARRAY" or next "START_ARRAY" jp.nextToken(); result.add(sv); } return result; } @Override public OpenSkyStates deserialize(JsonParser jp, DeserializationContext dc) throws IOException { if (jp.getCurrentToken() != null && jp.getCurrentToken() != JsonToken.START_OBJECT) { throw dc.mappingException(OpenSkyStates.class); } try { OpenSkyStates res = new OpenSkyStates(); for (jp.nextToken(); jp.getCurrentToken() != null && jp.getCurrentToken() != JsonToken.END_OBJECT; jp.nextToken()) { if (jp.getCurrentToken() == JsonToken.FIELD_NAME) { if ("time".equalsIgnoreCase(jp.getCurrentName())) { int t = jp.nextIntValue(0); res.setTime(t); } else if ("states".equalsIgnoreCase(jp.getCurrentName())) { jp.nextToken(); res.setStates(deserializeStates(jp)); } else { // ignore other fields, but consume value jp.nextToken(); } } // ignore others } return res; } catch (JsonParseException jpe) { throw dc.mappingException(OpenSkyStates.class); } } }