nitesh_
nitesh_

Reputation: 31

gson deserializing a field as null

First of all, I'd like to say this is for a university project.

I have 3 classes. Order abstract class and Delivery and DineIn classes which inherits from Order.

I am using Gson to serialize/deserialize the child classes but I have run into a bit of a problem. The Order class has a field orderType which gson uses to determine type of order it is, DineIn or Delivery.

Serialization is working just fine. The problem is that whenever I try to deserialize, the type field value is not read and is always set as null even though it is present in the JSON file. This happens when there are a lot of fields in Order because when I tried testing this program on a smaller scale with the Order class just having 2 fields (orderType and orderNo) everything worked just fine. I don't what I am doing wrong. I have tried searching on this site and am almost always coming across suggestions to make custom type adapters and serializers but we haven't studied about them in university and I don't want to use them (the instructor deducts marks for using anything he hasn't taught, I almost failed a course I took from him last time because I used things he hadn't taught. He doesn't seem to have a problem with third-party libraries though).

The code:

public class Main {
    public static final List<Order> ordersList = read();
    public static void main(String[] args) {
        System.out.println(ordersList.get(0).getOrderType());
        System.out.println(ordersList.get(0) instanceof DineIn ? "DineIn": "Delivery");
    }

    private static List<Order> read(){
        List<Order> ordersList = new ArrayList<>();
        Type type = new TypeToken<ArrayList<Order>>() {
        }.getType();


        RuntimeTypeAdapterFactory<Order> adapter = RuntimeTypeAdapterFactory.of(Order.class, "orderType")
                .registerSubtype(DineIn.class)
                .registerSubtype(Delivery.class);

        Gson gson = new GsonBuilder().registerTypeAdapterFactory(adapter).create();
        JsonReader ordersJsonReader;
        try {
            ordersJsonReader = new JsonReader(new FileReader("orders.json"));
            List<Order> tempOrdersList = gson.fromJson(ordersJsonReader, type);
            if (tempOrdersList != null) ordersList = tempOrdersList;
            ordersJsonReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ordersList;
    }
}

abstract class Order {
    private final int orderNumber;
    private final String date, customerName;
    private final int discountRate;
    private final String paymentMethod;
    private String orderStatus;
    private int grossTotal = 0;
    private double netTotal = 0;
    private int totalItems = 0;
    protected final String orderType;

    public abstract String getOrderType();
    public abstract double getExtraCharges();

    public Order(int orderNumber, String date, String customerName, int discountRate, String paymentMethod, String orderStatus, int grossTotal, double netTotal, int totalItems, String orderType) {
        this.orderNumber = orderNumber;
        this.date = date;
        this.customerName = customerName;
        this.discountRate = discountRate;
        this.paymentMethod = paymentMethod;
        this.orderStatus = orderStatus;
        this.grossTotal = grossTotal;
        this.netTotal = netTotal;
        this.totalItems = totalItems;
        this.orderType = orderType;
    }
}

class DineIn extends Order {
    private double serviceCharges = 150;

    public DineIn(int orderNumber, String date, String customerName, int discountRate, String paymentMethod, String orderStatus, int grossTotal, double netTotal, int totalItems) {
        super(orderNumber, date, customerName, discountRate, paymentMethod, orderStatus, grossTotal, netTotal, totalItems, "DineIn");
    }

    @Override
    public String getOrderType() {
        return orderType;
    }

    @Override
    public double getExtraCharges() {
        return serviceCharges;
    }
}

class Delivery extends Order {
    private double deliveryCharges = 100;

    public Delivery(int orderNumber, String date, String customerName, int discountRate, String paymentMethod, String orderStatus, int grossTotal, double netTotal, int totalItems) {
        super(orderNumber, date, customerName, discountRate, paymentMethod, orderStatus, grossTotal, netTotal, totalItems, "Delivery");
    }

    @Override
    public String getOrderType() {
        return orderType;
    }

    @Override
    public double getExtraCharges() {
        return deliveryCharges;
    }
}

The JSON:

[
  {
    "serviceCharges": 150.0,
    "orderNumber": 1,
    "date": "12/12/2021",
    "customerName": "Ali",
    "discountRate": 15,
    "paymentMethod": "Card",
    "orderStatus": "Preparing",
    "grossTotal": 5000,
    "netTotal": 4500.0,
    "totalItems": 14,
    "orderType": "DineIn"
  }
]

Upvotes: 2

Views: 750

Answers (1)

Stefan Zhelyazkov
Stefan Zhelyazkov

Reputation: 2981

In your code you have a hierarchy where DineIn and Delivery extend from Order. The way the orderType field is set is through an explicit String argument in the super() constructor.

However, Gson does not use the constructor to instantiate the objects. It uses a special no-argument constructor and populates the values via reflection: https://stackoverflow.com/a/40442037/9698467

In this specific case the problem comes from the RuntimeTypeAdapterFactory, which removes the orderType field from the JSON that it reads. The source code here confirms that: https://github.com/google/gson/blob/86d88c32cf6a6b7a6e0bbc855d76e4ccf6f120bb/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L202

As @fluffy suggested newer versions of the library include the maintain flag, which should allow for the field to be preserved: https://github.com/google/gson/blob/c1e7e2d2808b042cbe47ca31869ee6ccc62c5417/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L214

Upvotes: 1

Related Questions