如果我想用gson从一个json文件中读取pair对象,我应该使用什么java类?

py49o6xq  于 2022-11-06  发布在  Java
关注(0)|答案(4)|浏览(329)

我正在用gson编写json文件(在Java中),但我还想把它们读回去。(其中的键只有在运行时才知道),所以我想使用一个pair类来表示它们,我目前使用的是org.apache.commons.lang3.tuple中的pair类,但它没有像gson那样的noArg构造函数,需要将数据读回,因此当我执行这一步时,代码会失败。是否有其他的类可以更好地工作?下面是我想要的代码的一个小版本。

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);
zzlelutf

zzlelutf1#

我看到几个选项:
1.您可以使用MutablePair代替Pair。它应该可以立即使用。
1.您可以使用Map<String,Object>而不是ArrayList<Pair<String, Object>>。此解决方案仅在参数键唯一时有效。
1.您可以创建自己的Parameter类。
1.如果您坚持使用Pair,并且希望保持它的不可变性(实际上这是有意义的),则需要实现自己的TypeAdapter
1.最后但同样重要的是:请使用Jackson而不是Gson。

omvjsjqw

omvjsjqw2#

TL;DR:Gson不能使用Pair,因为没有一种方法可以示例化它(至少Gson知道:除非您告诉Gson使用Pair.of)。
很抱歉,您的解决方案存在一些问题。

  • 不要在可以使用List的声明站点绑定到ArrayList
  • ArrayList扩展Parameters是一个相当糟糕的选择(ArrayList有那么特殊吗?如果将来需要LinkedList怎么办?如果需要Set怎么办?哪一组?)--更喜欢聚合和封装集合。
  • Pair可以替换为Map.Entry--尽可能使用最通用的类型(类似于前一项)。
  • 令人惊讶的是,对这样的对/条目使用List可能比使用Map更好(如果需要以多Map方式存储多个键呢?)
  • Gson,不幸的是我不会让您轻松地序列化/反序列化Object,因为 * 没有办法从JSON重新构造 * 原始对象(Gson如何知道要反序列化的类有成千上万?哪一个更好?为什么要花费启发式或将类型标记直接放入JSON中,让Gson处理原始对象类(如果可能,包括参数化)?)--有关类似问题的更多评论,请参阅“当对象是另一个对象的字段时,为什么GSON无法转换对象?”。

让它与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();
    }

}

上面的代码非常简单,它还使用数组将对打包到中,因为您可能希望存储重复的键并保存一些空间(此外,两元素数组很可能是对的良好载体)。
第一个
中间JSON如下所示:

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

使用Gson的另一个想法(有点疯狂)是为Object实现一个启发式类型适配器,该适配器使用自定义的类型使用注解进行注解,比如@Heuristics(Java 8沿着了这种注解)。Pair<String, @Heuristics Object>,但据我所知,Gson无法在类型标记内携带类型使用注解,因此类型适配器工厂无法获取它们并应用自定义类型适配器(注意:为Object实现这样的类型适配器(即使是那些未注解的类型适配器)也可能导致不希望的全局效果)。
根据您的新信息,I * 可能 * 是这样实现的(没有太多的注解,但如果有更多的其他需求,可以重新实现):
第一个问题

pkwftd7m

pkwftd7m3#

如果你检查Pair source code,你会发现它是一个抽象类,没有字段,只有一些getter和setter。GSON需要字段(抽象类或接口不能被示例化,所以在反序列化时会失败)。See this for example
Mafor's answer建议的第一个选项MutablePairabstract class Pair的一个实现,它有Gson要使用的字段。

public L left;
public R right;

这是一个可行的选择。

Jackson可以使用getter和setter。但是,如果您将类型声明为Pair而不是MutablePair,没有setter(也没有字段),那么反序列化可能仍然会失败。因此Jackson可能不是唯一的答案。如果您使用MutablePair,Gson也可以工作。

我认为您应该考虑创建自己MyPair类,而不是试图找到一些现有的“标准”

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

或者重构您的设计,以便在没有冲突键或出现冲突键时使用Map,例如,使用接口MultiValuedMap的一些合适的实现

4urapxun

4urapxun4#

我最终得到的(在与我的客户讨论之后)基本上是这样的:

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

Map只Map一个东西,所以@Mafor建议的MutablePair可能会更好,但是我的客户要求使用Map,尽管它每个Map只有一个对,这是矫枉过正。
此外,正如与@fluffy讨论的那样,我使用ArrayList为diff中的代码获取.iterator()成员函数,该函数按顺序比较参数(相同的参数按不同的顺序表示其他含义(例如,copy A B与copy B A不同)。

相关问题