【问题标题】:Convert nested JSON to nested Java object using gson TypeAdapter使用 gson TypeAdapter 将嵌套的 JSON 转换为嵌套的 Java 对象
【发布时间】:2017-07-27 10:57:31
【问题描述】:

我正在尝试使用 google gson TypeAdapter 将嵌套 JSON 转换为嵌套 Java 对象,每个类都实现了 TypeAdapter。但我不想在单个适配器类中编写完整的 read() 方法逻辑。我在网上提到了几个问题和blog 示例。但是完整的读取逻辑在单个类中。

对于小的嵌套对象,在单个适配器中有逻辑很好,但对于大对象(每个类中有超过 10-15 个字段)就不好了。

[更新]

例如,json 键看起来与类属性相同,但实际上我将输入 hyphen-separated-small-case 键而不是 Camel case 键。所以我的 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?

【问题讨论】:

  • 您是否尝试过使用阅读器的默认实现?还是您有特定的理由编写自己的适配器?
  • 这和杰克逊有什么关系??请删除标签
  • 看起来你只需要 POJO 映射,而类型适配器在这里真的是大材小用:final Employee employee = gson.fromJson(..., Employee.class) 似乎已经完成了。
  • @LyubomyrShaydariv:更新问题。谢谢你说清楚。
  • @StefanLindner :是的,例如,我仅将示例 json 复制为类属性,但实际上我必须考虑 连字符分隔的小写 属性名称。这就是为什么我需要编写自定义的。

标签: java json gson json-deserialization


【解决方案1】:

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

(在下面的示例中,我将“rawType”传递给 TypeAdapter 实例,因为它通常很有用。如果不需要,请将其拉​​出。)

示例类型适配器工厂:

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

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,并找到了适合我的解决方案。

    您可以借助Gson 对象及其方法getAdapter(Class&lt;T&gt; type) 获得一个新的TypeAdapter&lt;T&gt; 实例。

    所以您提供的示例如下所示:

    Java Bean:

    //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 关于为每个读取调用创建一个新 Gson 实例的性能影响的观点之外,“TypeAdapter
      addressAdapter = new Gson().getAdapter(Address.class)”将不起作用,因为 AddressAdapter 尚未在您正在创建的新 Gson 实例中注册。
    【解决方案3】:

    您可以创建一个封装在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&lt;Address&gt; 是没有用的。
    猜你喜欢
    • 2014-07-18
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-02
    • 2019-07-04
    • 1970-01-01
    相关资源
    最近更新 更多