简体   繁体   中英

how to deserialize some specific fields only from Json using Gson?

I have the following JSON string

 [
  {
    "channel": "/bvmt/initValues",
    "data": {
      "value": {
        "instrumentIds": "['TN0007250012','TN0007500010']",
        "instruments": "[{'mnemonic':'ADWYA','marche':'ADWYA','phaut':5.82,'open':5.82,'nbrTrans':7,'veille':5.82,'time':'11:14:28','recapChange':0.00,'state':'','variation':'.','ref':5.82,'stateGrp':'S','percentChange':-0.34,'last':5.80,'bestTransaction':[{'value':5.82,'qte':'3','time':'10:00:00'},{'value':5.82,'qte':'5','time':'10:02:26'},{'value':5.82,'qte':'145','time':'10:23:27'},{'value':5.81,'qte':'100','time':'10:23:42'},{'value':5.80,'qte':'1000','time':'10:23:42'},{'value':5.73,'qte':'1','time':'10:31:21'},{'value':5.80,'qte':'100','time':'11:14:28'}],'volume':7857.19,'id':'TN0007250012','bestLimits':[{'quantiteAchat':2600,'timeVente':'11:44:10','ordreAchat':1,'prixAchat':5.76,'quantiteVente':100,'timeAchat':'11:44:10','ordreVente':1,'prixVente':5.90},{'quantiteAchat':50,'timeVente':'11:44:10','ordreAchat':1,'prixAchat':5.74,'quantiteVente':210,'timeAchat':'11:44:10','ordreVente':1,'prixVente':5.95},{'quantiteAchat':250,'timeVente':'11:44:10','ordreAchat':2,'prixAchat':5.75,'quantiteVente':187,'timeAchat':'11:44:10','ordreVente':1,'prixVente':5.94},{'quantiteAchat':189,'timeVente':'11:44:10','ordreAchat':3,'prixAchat':5.73,'quantiteVente':1112,'timeAchat':'11:44:10','ordreVente':1,'prixVente':5.97},{'quantiteAchat':44,'timeVente':'11:44:10','ordreAchat':1,'prixAchat':5.72,'quantiteVente':400,'timeAchat':'11:44:10','ordreVente':1,'prixVente':5.98}],'openStatus':'','cto':0,'valuer':'ADWYA','pbas':5.73,'grp':'S','abrv':'ADWYA','houv':'','qto':0,'seuilBas':5.65,'vto':0,'quantite':1354,'seuilHaut':5.99},{'mnemonic':'WIFAK','marche':'WIFAK','phaut':7.11,'open':7.00,'nbrTrans':2,'veille':7.13,'time':'10:24:15','recapChange':0.00,'state':'','variation':'.','ref':7.13,'stateGrp':'S','percentChange':-0.28,'last':7.11,'bestTransaction':[{'value':7.00,'qte':'99','time':'10:17:00'},{'value':7.11,'qte':'100','time':'10:24:15'}],'volume':1404.00,'id':'TN0007200017','bestLimits':[{'quantiteAchat':100,'timeVente':'11:00:19','ordreAchat':1,'prixAchat':6.80,'quantiteVente':100,'timeAchat':'11:00:19','ordreVente':1,'prixVente':7.09},{'quantiteAchat':0,'timeVente':'11:00:19','ordreAchat':0,'prixAchat':0.00,'quantiteVente':82,'timeAchat':'11:00:19','ordreVente':1,'prixVente':7.11},{'quantiteAchat':0,'timeVente':'11:00:19','ordreAchat':0,'prixAchat':0.00,'quantiteVente':284,'timeAchat':'11:00:19','ordreVente':2,'prixVente':7.10},{'quantiteAchat':0,'timeVente':'11:00:19','ordreAchat':0,'prixAchat':0.00,'quantiteVente':222,'timeAchat':'11:00:19','ordreVente':1,'prixVente':7.12},{'quantiteAchat':0,'timeVente':'11:00:19','ordreAchat':0,'prixAchat':0.00,'quantiteVente':110,'timeAchat':'11:00:19','ordreVente':2,'prixVente':7.13}],'openStatus':'','cto':0,'valuer':'WIFACK INT BANK','pbas':7.00,'grp':'S','abrv':'WIFAK','houv':'','qto':0,'seuilBas':6.92,'vto':0,'quantite':199,'seuilHaut':7.34}]"
      },
      "action": "initValues",
      "infoSession": {
        "lastInstrumentOrder": 11672,
        "state": 1,
        "lastInstrumentTime": "12:03:00",
        "tradingTime": "08:04:02",
        "tradingDate": "2017-04-24"
      }
    },
    "id": "5"
  },
  {
    "channel": "/bvmt/process",
    "successful": true,
    "id": "5"
  }
]

I'm interested only in the content of the "instruments" field , I want to get only the "mnemonic" and "percentChange" fields and deserialize them into an array of Objects like this

public class Data 
{
    public List<MyObject> objects;
}

public class MyObject
{
    String mnemonic;
    Float percentChange;
}

How can I do this using Gson ?

Actually you have dozen ways of doing it. It only depends on how you manage your JSON documents. Let's declare a couple of DTOs first:

final class Data {

    final List<MyObject> objects;

    Data(final List<MyObject> objects) {
        this.objects = objects;
    }

}
final class MyObject {

    final String mnemonic;
    final Float percentChange;

    MyObject(final String mnemonic, final Float percentChange) {
        this.mnemonic = mnemonic;
        this.percentChange = percentChange;
    }

}

Here are some ways:

Pure JsonElement trees

The following example uses Java 8 Stream API and Gson JSON trees facilities, and it appears to be the simplest way to me:

private static final Gson gson = new Gson();

static Data testUsingJsonTreesOnly(final Reader reader) {
    final List<MyObject> objects = StreamSupport.stream(gson.fromJson(reader, JsonElement.class).getAsJsonArray().spliterator(), false)
            .map(JsonElement::getAsJsonObject)
            .map(jsonObject -> jsonObject.get("data"))
            .filter(Objects::nonNull)
            .map(JsonElement::getAsJsonObject)
            .map(jsonObject -> jsonObject.get("value"))
            .filter(Objects::nonNull)
            .map(JsonElement::getAsJsonObject)
            .map(jsonObject -> jsonObject.get("instruments"))
            .filter(Objects::nonNull)
            .map(JsonElement::getAsJsonPrimitive)
            .map(JsonPrimitive::getAsString)
            .map(json -> gson.fromJson(json, JsonElement.class))
            .map(JsonElement::getAsJsonArray)
            .flatMap(jsonArray -> StreamSupport.stream(jsonArray.spliterator(), false))
            .map(jsonElement -> gson.fromJson(jsonElement, MyObject.class))
            .collect(toList());
    return new Data(objects);
}

Two-pass mappings

This approach way attempts to extract the values in two passes:

  • deserialize the "outer" object;
  • get the inner object string and deserialize it in the second pass.
private static final Gson gson = new Gson();

private static final Type channelViewListType = new TypeToken<List<ChannelView>>() {
}.getType();

private static final Type myObjectListType = new TypeToken<List<MyObject>>() {
}.getType();

static Data testUsingDeserializationWithStrings(final Reader reader) {
    final List<MyObject> objects = gson.<List<ChannelView>>fromJson(reader, channelViewListType)
            .stream()
            .filter(Objects::nonNull)
            .map(view -> view.data)
            .filter(Objects::nonNull)
            .map(view -> view.value)
            .filter(Objects::nonNull)
            .map(view -> view.instruments)
            .map((Function<String, List<MyObject>>) instruments -> gson.fromJson(instruments, myObjectListType))
            .flatMap(Collection::stream)
            .collect(toList());
    return new Data(objects);
}

private static final class ChannelView {

    final DataView data = null;

}

private static final class DataView {

    final ValueView value = null;

}

private static final class ValueView {

    final String instruments = null;

}

One-pass mappings using type adapters

This is, I would say, level #3: you can implement a specific type adapter to "unwrap" the encoded JSON document. @JsonAdapter can be used to specified the field that contains the specific "inner" JSON document:

private static final Gson gson = new Gson();

private static final Type channelViewListType = new TypeToken<List<ChannelView>>() {
}.getType();

static Data testUsingDeserializationWithJsonAdapter(final Reader reader) {
    final List<MyObject> objects = gson.<List<ChannelView>>fromJson(reader, channelViewListType)
            .stream()
            .filter(Objects::nonNull)
            .map(view -> view.data)
            .filter(Objects::nonNull)
            .map(view -> view.value)
            .filter(Objects::nonNull)
            .map(view -> view.instruments)
            .flatMap(Collection::stream)
            .collect(toList());
    return new Data(objects);
}


private static final class ChannelView {

    final DataView data = null;

}

private static final class DataView {

    final ValueView value = null;

}

private static final class ValueView {

    @JsonAdapter(UnpackedJsonTypeAdapterFactory.class)
    final List<MyObject> instruments = null;

}

private static final class UnpackedJsonTypeAdapterFactory
        implements TypeAdapterFactory {

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        return new UnpackedJsonTypeAdapter<>(gson.getAdapter(typeToken));
    }

    private static final class UnpackedJsonTypeAdapter<T>
            extends TypeAdapter<T> {

        private final TypeAdapter<T> typeAdapter;

        private UnpackedJsonTypeAdapter(final TypeAdapter<T> typeAdapter) {
            this.typeAdapter = typeAdapter;
        }

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final T value)
                throws IOException {
            out.value(typeAdapter.toJson(value));
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            final String json = in.nextString();
            final JsonReader lenientIn = new JsonReader(new StringReader(json));
            lenientIn.setLenient(true);
            return typeAdapter.read(lenientIn);
        }

    }

}

Pure streaming

Probably the easiest by concept way, but not that easy to implement because of creating a high-level parser that deals with JSON token stream directly from the beginning to the end. Note that no even Gson instances are introduced.

static Data testUsingStreaming(final Reader reader)
        throws IOException {
    final List<MyObject> myObjects = new ArrayList<>();
    final JsonReader jsonReader = new JsonReader(reader);
    jsonReader.beginArray();
    while ( jsonReader.hasNext() ) {
        jsonReader.beginObject();
        while ( jsonReader.hasNext() ) {
            switch ( jsonReader.nextName() ) {
            case "data":
                jsonReader.beginObject();
                while ( jsonReader.hasNext() ) {
                    switch ( jsonReader.nextName() ) {
                    case "value":
                        jsonReader.beginObject();
                        while ( jsonReader.hasNext() ) {
                            switch ( jsonReader.nextName() ) {
                            case "instruments":
                                final String instrumentsJson = jsonReader.nextString();
                                parseInstruments(instrumentsJson, myObjects);
                                break;
                            default:
                                jsonReader.skipValue();
                                break;
                            }
                        }
                        jsonReader.endObject();
                        break;
                    default:
                        jsonReader.skipValue();
                        break;
                    }
                }
                jsonReader.endObject();
                break;
            default:
                jsonReader.skipValue();
                break;
            }
        }
        jsonReader.endObject();
    }
    jsonReader.endArray();
    return new Data(myObjects);
}

private static void parseInstruments(final String instrumentsJson, final Collection<MyObject> myObjects)
        throws IOException {
    final JsonReader jsonReader = new JsonReader(new StringReader(instrumentsJson));
    jsonReader.setLenient(true);
    jsonReader.beginArray();
    while ( jsonReader.hasNext() ) {
        String mnemonic = null;
        Float percentChange = null;
        jsonReader.beginObject();
        while ( jsonReader.hasNext() ) {
            final String name = jsonReader.nextName();
            switch ( name ) {
            case "mnemonic":
                mnemonic = jsonReader.nextString();
                break;
            case "percentChange":
                percentChange = (float) jsonReader.nextDouble();
                break;
            default:
                jsonReader.skipValue();
                break;
            }
        }
        if ( mnemonic != null && percentChange != null ) {
            myObjects.add(new MyObject(mnemonic, percentChange));
        }
        jsonReader.endObject();
    }
    jsonReader.endArray();
}

All of the approaches above produce the same output:

ADWYA: -0.34
WIFAK: -0.28

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM