De-serializing Exception with FastJSON: Can not set field to java.lang.Exception

864 Views Asked by At

I'm using FastJSON to serialize and de-serialize some classes. However, I've found a combination that doesn't work: An exception that contains holds a reference to an object from another class cannot be de-serialized. Example:

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

public class Main {
    @AllArgsConstructor
    @Getter
    @Setter
    private static class A {
        private String a;
        private String b;
    }

    @AllArgsConstructor
    @Getter
    @Setter
    private static class E extends Exception {
        private A a;
    }

    public static void main(String[] args) {
        E original = new E(new A("hello", "world"));
        String serialized = JSON.toJSONString(original);
        E deserialized = JSON.parseObject(serialized, E.class); // throws below exception
    }
}

This example yields the following runtime error:

Exception in thread "main" com.alibaba.fastjson.JSONException: set property error, com.example.Main$E#a
    at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:183)
    at com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer.deserialze(ThrowableDeserializer.java:149)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:688)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:396)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:300)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:573)
    at com.example.Main.main(Main2.java:40)
Caused by: java.lang.IllegalArgumentException: Can not set com.example.Main$A field com.example.Main$E.a to java.lang.Exception
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:75)
    at java.lang.reflect.Field.set(Field.java:764)
    at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:178)
    ... 6 more

It tells me that it cannot set the field a in E to an Exception object. It looks like a bug in FastJSON because I'd expect it to de-serialize the value of a to an object of A, not E. Or did I made a mistake? What causes this problem and what has to be changed in order to circumvent this?

1

There are 1 best solutions below

0
On

When de-serializing a throwable, FastJSON first instantiates the throwable object, then sets its attributes. To instantiate the object, it looks for three different constructors:

  1. Default constructor (no parameters)
  2. Constructor with an error message as input
  3. Constructor with an error message and a cause as input

If none of these is available, it defaults to instantiating an Exception object.

However, I defined none of the above constructors. Hence, FastJSON instantiated an Exception object. Later, it tried to set the attribute .a of this exception object to the reference of the A instance. This doesn't work because Exception has no such attribute.

Furthermore, my internal classes were declared private, so FastJSON cannot access them.

The solution is simple:

  1. Create the required constructor
  2. Make the classes public
public static class E extends Exception {
    private A a;
    
    public E(String message, Throwable cause) {
        super(message, cause);
    }
}