簡體   English   中英

如果我想從 json 文件與 Z67166080962DE308CD762A 一起讀取配對對象,我應該使用什么 java class?

[英]What java class should I use for pair objects if I want to read them in from a json file with gson?

我正在用 gson (在 Java 中)編寫 json 文件,但我也想將它們讀回。而且,我有一些表示鍵值對的數據(其中鍵僅在運行時知道),所以我想使用pair class to represent them, I am currently using the pair class from org.apache.commons.lang3.tuple, but it doesn't have a noArg constructor like gson needs to read the data back in, and thus the code fails when I做那一步。 還有其他一些 class 會更好嗎? 這是我想要的代碼的一個小版本。

import org.apache.commons.lang3.tuple.Pair;
import com.google.gson.GsonBuilder;
import com.google.gson.GsonBuilder;

class JsonFile { ArrayList<Event> events; 
  Public void diff(JsonFile other) {
    // code not shown, but checks some things for being equal
  } 
}

class Event { String type; // of event
  Parameters params;
  Public Event(String type, Parameters params) { 
    this.type = type;
    this.params = params;
  }
}

class Parameters extends ArrayList<Pair<String, Object>> {}

JsonFile eventsToWrite; // populated by code
JsonFile eventstoDiff; // a stored copy of what the file "should" look like

// ...
// Some code filling eventsToWrite
   Parameters params = new Parameters();
   params.add(Pair.of("xyzzy", "is a magic name"));
   params.add(Pair.of("foo", "bar");
   Event event = new Event("lispy event", params);
   eventsToWrite.add(event);
// ...

Reader reader new BufferedReader(new FileReader(new File("toDiff.json")));
GsonBuilder bld = new GsonBuilder();
Gson gsonRead = bld.create();
eventsToDiff = gsonRead.fromJson(reader, JsonFile.class); // this fails
eventsToWrite.diff(eventsToDiff);

我看到幾個選項:

  1. 您可以使用MutablePair代替Pair 它應該開箱即用。
  2. 您可以使用Map<String,Object>而不是ArrayList<Pair<String, Object>> 僅當參數鍵是唯一的時,此解決方案才有效。
  3. 您可以創建自己的Parameter class。
  4. 如果您堅持使用Pair並且希望保持它不可變(實際上這是有道理的),則需要實現自己的TypeAdapter
  5. 最后但同樣重要的是:使用Jackson而不是 Gson。

TL;DR:Gson 不能使用Pair ,因為沒有辦法實例化它(至少 Gson 知道:除非你告訴 Gson 使用Pair.of )。

很抱歉,您的解決方案存在一些問題。

  • 不要在可以使用List的聲明站點上綁定到ArrayList
  • ArrayList擴展Parameters是一個相當糟糕的選擇( ArrayList有那么特別嗎?如果你以后想要LinkedList怎么辦?如果你需要一個Set來代替?哪個集合?)——更喜歡聚合和封裝集合。
  • Pair可以用Map.Entry替換——盡可能使用最通用的類型(與上一項類似)。
    • 令人驚訝的是,對此類對/條目使用List可能比使用Map更好(如果需要以多映射方式存儲多個鍵怎么辦?)
  • Gson, unfortunately, won't let you serialize/ Object easily because there is no way to reconstruct the original object from the JSON (how should Gson know which class to deserialize with whereas there are tens thousand classes? which one is preferrable? why pay for heuristics or baking in type tags right into JSON to let Gson the original object class (including parameterization if possible)?) -- see some more comments on a similar issue at Why GSON fails to convert Object when it's a field of another Object ? .

使其與 Gson 一起工作的一種可能方法是遷移到字符串-字符串對(這樣 Gson 就可以知道如何制作 JSON 類型往返適配器工廠和鍵值對)。

final class PairTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory instance = new PairTypeAdapterFactory();

    private PairTypeAdapterFactory() {
    }

    static TypeAdapterFactory getInstance() {
        return instance;
    }

    @Override
    @Nullable
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( !Pair.class.isAssignableFrom(typeToken.getRawType()) ) {
            return null;
        }
        final ParameterizedType parameterizedType = (ParameterizedType) typeToken.getType();
        final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        final TypeAdapter<Pair<Object, Object>> pairTypeAdapter = resolveTypeAdapter(gson, actualTypeArguments[0], actualTypeArguments[1]);
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) pairTypeAdapter;
        return typeAdapter;
    }

    private <L, R> TypeAdapter<Pair<L, R>> resolveTypeAdapter(final Gson gson, final Type leftType, final Type rightType) {
        @SuppressWarnings("unchecked")
        final TypeAdapter<L> leftTypeAdapter = (TypeAdapter<L>) gson.getDelegateAdapter(this, TypeToken.get(leftType));
        @SuppressWarnings("unchecked")
        final TypeAdapter<R> rightTypeAdapter = (TypeAdapter<R>) gson.getDelegateAdapter(this, TypeToken.get(rightType));
        return new TypeAdapter<Pair<L, R>>() {
            @Override
            public void write(final JsonWriter out, final Pair<L, R> value)
                    throws IOException {
                out.beginArray();
                leftTypeAdapter.write(out, value.getLeft());
                rightTypeAdapter.write(out, value.getRight());
                out.endArray();
            }

            @Override
            public Pair<L, R> read(final JsonReader in)
                    throws IOException {
                in.beginArray();
                final L left = leftTypeAdapter.read(in);
                final R right = rightTypeAdapter.read(in);
                in.endArray();
                return Pair.of(left, right);
            }
        }
                .nullSafe();
    }

}

上面的代碼非常簡單,它還使用 arrays 來打包對,因為您可能想要存儲重復的鍵並節省一些空間(此外,二元素數組很可能是對的良好載體)。

@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class Event {

    String type;
    List<Pair<String, String>> params;

}
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class JsonFile {

    List<Event> events;

}
private static final Gson gson = new GsonBuilder()
        .disableHtmlEscaping()
        .disableInnerClassSerialization()
        .registerTypeAdapterFactory(PairTypeAdapterFactory.getInstance())
        .create();

@Test
public void test() {
    final JsonFile before = new JsonFile();
    final List<Pair<String, String>> params = new ArrayList<>();
    params.add(Pair.of("xyzzy", "is a magic name"));
    params.add(Pair.of("foo", "bar"));
    final Event event = new Event("lispy event", params);
    before.events = new ArrayList<>();
    before.events.add(event);
    final String json = gson.toJson(before, JsonFile.class);
    final JsonFile after = gson.fromJson(json, JsonFile.class);
    Assertions.assertEquals(before, after);
}

中間JSON如下:

{
  "events": [
    {
      "type": "lispy event",
      "params": [
        [
          "xyzzy",
          "is a magic name"
        ],
        [
          "foo",
          "bar"
        ]
      ]
    }
  ]
}

Another (somewhat crazy) idea on using Gson is implementing an heuristics type adapter for Object that are annotated with a custom type-use annotation, say, @Heuristics (these kind of annotations came along with Java 8). 這將允許您像這樣參數化對: Pair<String, @Heuristics Object> ,但據我所知, Gson 無法在類型標記中攜帶類型使用注釋,因此類型適配器工廠無法獲取它們並應用自定義類型適配器(注意:為Object實現這樣的類型適配器,即使是那些沒有注釋的,也可能會導致不需要的全局效果)。


根據您對此的新信息,我可能會這樣實現(沒有太多評論,但如果有更多其他要求可以重新實現):

@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class JsonFile {

    List<Event<?>> events;

}
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class Event<T> {

    String type;
    Map<String, T> params;

}
final class EventTypeAdapterFactory
        implements TypeAdapterFactory {

    private final Map<String, Class<?>> typeMap;

    private EventTypeAdapterFactory(final Map<String, Class<?>> typeMap) {
        this.typeMap = typeMap;
    }

    static TypeAdapterFactory getInstance(final Map<String, Class<?>> typeMap) {
        return new EventTypeAdapterFactory(typeMap);
    }

    @Override
    @Nullable
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( !Event.class.isAssignableFrom(typeToken.getRawType()) ) {
            return null;
        }
        final Map<String, TypeAdapter<?>> typeAdapterMap = typeMap.entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> gson.getDelegateAdapter(this, TypeToken.getParameterized(Map.class, String.class, e.getValue()))));
        final TypeAdapter<Event<?>> typeAdapter = new TypeAdapter<Event<?>>() {
            @Override
            public void write(final JsonWriter out, final Event<?> value)
                    throws IOException {
                out.beginObject();
                out.name("type");
                out.value(value.type);
                out.name("params");
                @Nullable
                @SuppressWarnings("unchecked")
                final TypeAdapter<Object> paramsTypeAdapter = (TypeAdapter<Object>) typeAdapterMap.get(value.type);
                if ( paramsTypeAdapter == null ) {
                    throw new IllegalStateException(value.type + " is not mapped to a specific type");
                }
                paramsTypeAdapter.write(out, value.params);
                out.endObject();
            }

            @Override
            public Event<?> read(final JsonReader in) {
                final JsonObject root = Streams.parse(in)
                        .getAsJsonObject();
                final String type = root.getAsJsonPrimitive("type")
                        .getAsString();
                @Nullable
                @SuppressWarnings("unchecked")
                final TypeAdapter<Object> paramsTypeAdapter = (TypeAdapter<Object>) typeAdapterMap.get(type);
                if ( paramsTypeAdapter == null ) {
                    throw new IllegalStateException(type + " is not mapped to a specific type");
                }
                final JsonObject rawParams = root.getAsJsonObject("params");
                @SuppressWarnings("unchecked")
                final Map<String, ?> params = (Map<String, ?>) paramsTypeAdapter.fromJsonTree(rawParams);
                return new Event<>(type, params);
            }
        }
                .nullSafe();
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
        return castTypeAdapter;
    }

}
public final class EventTypeAdapterFactoryTest {

    private static final Gson gson = new GsonBuilder()
            .disableHtmlEscaping()
            .disableInnerClassSerialization()
            .registerTypeAdapterFactory(EventTypeAdapterFactory.getInstance(ImmutableMap.of("lispy event", String.class)))
            .create();

    @Test
    public void test() {
        final JsonFile before = new JsonFile();
        final Map<String, Object> params = new LinkedHashMap<>();
        params.put("xyzzy", "is a magic name");
        params.put("foo", "bar");
        final Event<Object> event = new Event<>("lispy event", params);
        before.events = new ArrayList<>();
        before.events.add(event);
        final String json = gson.toJson(before, JsonFile.class);
        System.out.println(json);
        final JsonFile after = gson.fromJson(json, JsonFile.class);
        Assertions.assertEquals(before, after);
    }

}
{
  "events": [
    {
      "type": "lispy event",
      "params": {
        "xyzzy": "is a magic name",
        "foo": "bar"
      }
    }
  ]
}

如果您檢查Pair源代碼,您會看到它是一個抽象的 class 沒有字段,但有一些 getter 和 setter。 GSON 需要字段(抽象 class 或接口無法實例化,因此在反序列化時失敗)。 參見這個例子

現在Mafor 的回答建議作為第一個選項 - MutablePair - 是這個abstract class Pair的實現。 它有供 Gson 使用的字段。

public L left;
public R right;

這是一個工作選擇。

Jackson可以使用 getter 和 setter。 但是,如果您將類型聲明為Pair而不是MutablePair則沒有設置器(也沒有字段),那么反序列化可能仍然會失敗。 所以 Jackson 可能不是答案。 如果您使用 MutablePair, MutablePair也可以工作。

我認為您應該考慮創建自己的MyPair class 而不是嘗試找到一些現有的“標准”

@Getter
@Setter
public class MyPair {
    private String left;
    private Object right;
}

或者在沒有沖突鍵或出現沖突鍵時重構您的設計以使用Map ,例如,使用接口MultiValuedMap的一些合適實現

我最終得到的(在與我的客戶討論后)基本上是這樣的:

@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
final class Event {
    String type;
    List<Map<String, Object>> params;  
}

map 只映射一件事,因此根據@Mafor 的建議 MutablePair 可能會更好,但我的客戶要求 Map,即使每個 Z1D78DC8ED51214E518B5114FE24490AE 只有一對並且過度殺傷。

此外,正如與@fluffy 討論的那樣,我使用 ArrayList 來獲取 .iterator() 成員 function 用於 diff 中的代碼,它按順序比較參數(相同的參數以不同的順序表示其他含義(例如,復制 AB 不是與復制 BA 相同)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM