使用gson的TypeAdapter将嵌套的JSON转换为嵌套的Java对象

5
我想使用 Google Gson TypeAdapter 将嵌套的 JSON 转换为嵌套的 Java 对象,并为每个类实现 TypeAdapter。但我不想在单个适配器类中编写完整的 read() 方法逻辑。我查阅了网上的一些问题和博客示例。但是完整的读取逻辑都在一个类中。
对于小的嵌套对象,单个适配器中的逻辑还可以,但对于大型对象(每个类中有10-15个以上字段)来说就不好了。 [更新] 例如,JSON 键看起来与类属性相同,但实际上我将获得输入作为连字符分隔的小写键而不是驼峰命名法键。因此,我的 JSON 和 Java 类属性名称将不相同,因此我必须编写自定义逻辑进行映射。
例如: 示例 Json 输入:
{
  "id": 1,
  "name": "Alex",
  "emailId": "alex@gmail.com",
  "address": {
    "address": "21ST & FAIRVIEW AVE",
    "district": "district",
    "city": "EATON",
    "region": "PA",
    "postalCode": "18044",
    "country": "US"
  }
}

Java Bean如下:

//Employee object class
public class Employee {

  private int id;
  private String name;
  private String emailId;
  private Address address;
  ..
}

//Address object class
public class Address {

  private String address;
  private String district;
  private String city;
  private String region;
  private String postalCode;
  private String country;
  ..
}

我希望有两个不同的适配器,并在read()方法中集成多个适配器。
public class EmployeeAdapter extends TypeAdapter<Employee> {
  @Override
  public void write(JsonWriter out, Employee employee) throws IOException {
    //
  }

  @Override
  public Employee read(JsonReader jsonReader) throws IOException {
    //read logic for employee class using AddressAdapter for address json
  }
}

public class AddressAdapter extends TypeAdapter<Address> {
  @Override
  public void write(JsonWriter out, Address address) throws IOException {
    //
  }

  @Override
  public Address read(JsonReader jsonReader) throws IOException {
    //read logic for Address class
  }
}

如何在EmployeeAdapter中使用AddressAdapter?

2
你尝试过使用默认的读取器实现吗?还是你有特定的原因要编写自己的适配器? - Stefan Lindner
这与Jackson有什么关系?请移除标签。 - Sharon Ben Asher
2
看起来你只需要POJO映射,类型适配器在这里真的是过度设计了:final Employee employee = gson.fromJson(..., Employee.class) 看起来已经足够了。 - Lyubomyr Shaydariv
@GovindS @SerializedName 可能会对您有所帮助 https://google.github.io/gson/apidocs/com/google/gson/annotations/SerializedName.html - Lyubomyr Shaydariv
@GovindS 是的,这是真的:ReflectiveTypeAdapterFactory.Adapter 由于反射而慢,但如果您要从外部资源中获取JSON,则比较取回资源所花费的时间更长。如果您真的认为 ReflectiveTypeAdapterFactory.Adapter 对您来说很慢,那么您要么必须创建一堆自定义类型适配器,要么只能使用 JsonReader 并手动逐个令牌解析构建结果对象。后者似乎具有最佳性能,因为您甚至不需要 GsonTypeAdapter 以及它们在幕后完成的工作。 - Lyubomyr Shaydariv
显示剩余3条评论
3个回答

5

我使用TypeAdapterFactory来处理这种情况。它允许将gson实例传递给TypeAdapter实例。

(在下面的示例中,我保留了将“rawType”传递给TypeAdapter实例的内容,因为这通常很有用。如果不需要,则可以去掉。)

示例TypeAdapterFactory:

public class ContactTypeAdapterFactory implements TypeAdapterFactory {

    // Add @SuppressWarnings("unchecked") as needed.

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        final Class<? super T> rawClass = typeToken.getRawType();
        if (Employee.class.isAssignableFrom(rawClass)) {
            // Return EmployeeAdapter for Employee class
            return EmployeeAdapter.get(rawClass, gson);
        }
        if (Address.class.isAssignableFrom(rawClass)) {
            // Return AddressAdapter for Address class
            return AddressAdapter.get(rawClass, gson);
        }
        return null; // let Gson find somebody else
    }

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

        private final Gson gson;
        private final Class<? super T> rawClass;  // Not used in this example

        private EmployeeAdapter(Class<? super T> rawClass, Gson gson) {
            this.rawClass = rawClass;
            this.gson = gson;
        }

        private static <T> TypeAdapter<T> get(Class<? super T> rawClass, Gson gson) {
            // Wrap TypeAdapter in nullSafe so we don't need to do null checks
            return new EmployeeAdapter<>(rawClass, gson).nullSafe();
        }

        @Override
        public void write(JsonWriter out, T value)
                throws IOException {

            // We should only ever be here for Employee types
            // Cast value to Employee
            Employee record = (Employee)value;

            // Output start of JSON object
            out.beginObject();

            // Output key / value pairs
            out.name("name");
            gson.getAdapter(String.class).write(out, record.getName());
            // [...]
            out.name("address");
            gson.getAdapter(Address.class).write(out, record.getAddress());

            // Output end of JSON object
            out.endObject();
        }

        @Override
        public T read(JsonReader in)
                throws IOException {

            String fieldName;

            // Create an empty Employee object
            Employee record = new Employee();

            // Consume start of JSON object
            in.beginObject();

            // Iterate each key/value pair in the json object
            while (in.hasNext()) {
                fieldName = in.nextName();
                switch (fieldName) {
                    case "name":
                        record.setName(gson.getAdapter(String.class).read(in));
                        break;
                    // [...] 
                    case "address":
                        record.setAddress(gson.getAdapter(Address.class).read(in));
                        break;
                    default:
                        // Skip any values we don't support
                        in.skipValue();
                }
            }
            // Consume end of JSON object
            in.endObject();

            // Return new Object
            return (T)record;
        }

    }

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

        private final Gson gson;
        private final Class<? super T> rawClass; // Not used in this example

        private AddressAdapter(Class<? super T> rawClass, Gson gson) {
            this.rawClass = rawClass;
            this.gson = gson;
        }

        private static <T> TypeAdapter<T> get(Class<? super T> rawClass, Gson gson) {
            // Wrap TypeAdapter in nullSafe so we don't need to do null checks
            return new AddressAdapter<>(rawClass, gson).nullSafe();
        }

        @Override
        public void write(JsonWriter out, T value)
                throws IOException {

            // We should only ever be here for Address types
            // Cast value to Address
            Address record = (Address)value;

            // Output start of JSON object
            out.beginObject();

            // Output key / value pairs
            out.name("address");
            gson.getAdapter(String.class).write(out, record.getName());
            // [...]
            out.name("country");
            gson.getAdapter(String.class).write(out, record.getCountry());

            // Output end of JSON object
            out.endObject();
        }

        @Override
        public T read(JsonReader in)
                throws IOException {

            String fieldName;

            Address record = new Address();
            in.beginObject();
            // Iterate each parameter in the json object
            while (in.hasNext()) {
                fieldName = in.nextName();
                switch (fieldName) {
                    case "address":
                        record.setAddress(gson.getAdapter(String.class).read(in));
                        break;
                    // [...]    
                    case "country":
                        record.setCountry(gson.getAdapter(String.class).read(in));
                        break;
                    default:
                        in.skipValue();
                }
            }
            in.endObject();
            return (T)record;

        }

    }

}

使用:

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(new ContactTypeAdapterFactory())
    .create();
Employee employee = gson.fromJson(jsonString, Employee.class);

3

我曾经遇到过同样的问题,但找到了适合我的解决方案。

您可以通过 Gson 对象及其方法 getAdapter(Class<T> type) 获取一个新的 TypeAdapter<T> 实例。

因此,您提供的示例将如下所示:

Java Beans:

//Employee object class
@JsonAdapter(EmployeeAdapter.class)
public class Employee {

  private int id;
  private String name;
  private String emailId;
  private Address address;
  ..
}

//Address object class
@JsonAdapter(AddressAdapter.class)
public class Address {

  private String address;
  private String district;
  private String city;
  private String region;
  private String postalCode;
  private String country;
  ..
}

类型适配器:

public class EmployeeAdapter extends TypeAdapter<Employee> {
  @Override
  public void write(JsonWriter out, Employee employee) throws IOException {
    //
  }

  @Override
  public Employee read(JsonReader jsonReader) throws IOException {
    Employee employee = new Employee();

    jsonReader.beginObject();
    //read your Employee fields

    TypeAdapter<Address> addressAdapter = new Gson().getAdapter(Address.class);
    employee.setAddress(addressAdapter.read(jsonReader);

    return employee;
  }
}

public class AddressAdapter extends TypeAdapter<Address> {
  @Override
  public void write(JsonWriter out, Address address) throws IOException {
    //
  }

  @Override
  public Address read(JsonReader jsonReader) throws IOException {
    Address address = new Address();
    //read your Address fields
    return address;
  }
}

使用这个方案,您可以享受松耦合的代码优势,因为唯一的依赖在于Beans中的JsonAdapter注解。
此外,您还可以将每个Bean的读/写逻辑拆分到自己的TypeAdapter中。

你在每次调用EmployeeAdapter#read()时都创建了一个新的Gson对象,我认为这样做不太好,因为Gson构造函数的开销相当大。 - Varvara Kalinina
除了Varvara提到的为每个读取调用创建新的Gson实例会对性能产生影响之外,“TypeAdapter<Address> addressAdapter = new Gson().getAdapter(Address.class)”也行不通,因为AddressAdapter没有在您创建的新Gson实例中注册。 - JohnK

0

您可以在EmployeeAdapter中创建一个新的AddressAdapter实例。请参考以下示例。

public class EmployeeAdapter extends TypeAdapter<Employee> {
    //private instance of address adapter
    private AddressAdapter addressAdapter = new AddressAdapter();

    @Override
    public void write(JsonWriter out, Employee employee) throws IOException {
        //TODO: do your stuff to Employee class

        //manually do it to Address class
        addressAdapter.write(out, employee.getAddress());
    }

    @Override
    public Employee read(JsonReader jsonReader) throws IOException {
        //your new instance of employee
        Employee employee = new Employee();

        //TODO: read logic for employee class using AddressAdapter for address json

        //read from Address class
        Address address = addressAdapter.read(jsonReader);//you may need only portion of address available, simply grab that string as same as other properties if needed
        employee.setAddress(address);
    }
}

这样做是可以的。但在 AddressAdapter 中扩展“TypeAdapter<Address>”是没有用的。 - GovindS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接