简体   繁体   中英

Include a certain transient field in JSON serialization with GSON

I have this class

Myclass
{
  transient String field1;
  transient String field2;
  ... // other non transient fields
}

I store serialized objects, use them through network this way (transient fields excluded).

However, just for one particular case, I need to include field2 in serialization.

Is there a way to not to exclude a certain transient field in serialization using gson?

Solution 0:

Use a custom type adapter for the class.

Considering

@JsonAdapter(KA.class)
class K{
    private transient String name;
    private transient String password;
}

class Entity_Adapter extends TypeAdapter<Entity>{
    @Override
    public void write(JsonWriter out, Entity value) throws IOException {
        out.beginObject();
        
        out.name("name");
        out.value(value.getName());
        
        out.name("password");
        out.value(value.getPassword());
        
        out.endObject();
    }

    @Override
    public Entity read(JsonReader in) throws IOException {
        Entity k=new Entity();
        in.beginObject();
        
        in.nextName();
        k.setName(in.nextString());
        
        in.nextName();
        k.setPassword(in.nextString());
        
        in.endObject();
        return k;
    }
}

full examplehere

Solution 1: (not robust)

Add another non- transient field, and always copy any new set value for field2 for it too. eg

transient String field1;
transient String field2;

@SerializedName("field2")
private String field2_non_trans;

public void setField2(String arg_val){
this.field2 = arg_val;
this.field2_non_trans = arg_val;
}

public String getField2(){
  if(field2 == null){
    field2 = field2_non_trans;
  } 
  return field2;
}

full samplehere

But you MUST track every change to that field2 , to keep the copy of the val for field2_non_trans updated always, so if that field2 is set by constructor, or out-of its setter function, you have to be sure you set the value copy for field2_non_trans

Same for deserializing, you have to either:

  • once deserializing is over, you need to set the deserialized value of field2_non_trans to field2 using a method.
  • Or simply return field2_non_trans by getField2() method, where field2 is null

Solution 2:

Mark that field2 non-transient.

You can duplicate your transient field with another non transient and write value from transient field setter. The idea is update clone field every time transient field updates.

Despite I would never recommend using the same class for different libraries due to issues like this, you can easily manage the way Gson applies exclusion strategies to fields being serialized and deserialized.

public final class TransientExclusionStrategy
        implements ExclusionStrategy {

    private static final ExclusionStrategy instance = new TransientExclusionStrategy();

    private TransientExclusionStrategy() {
    }

    public static ExclusionStrategy getInstance() {
        return instance;
    }

    @Override
    public boolean shouldSkipField(final FieldAttributes attributes) {
        @Nullable
        final Expose expose = attributes.getAnnotation(Expose.class);
        if ( expose == null ) {
            return attributes.hasModifier(Modifier.TRANSIENT);
        }
        return !expose.serialize();
    }

    @Override
    public boolean shouldSkipClass(final Class<?> clazz) {
        return false;
    }

}

This implementation would pass the following unit test:

public final class TransientExclusionStrategyTest {

    private static final String EXPOSED = "EXPOSED";
    private static final String IGNORED = "IGNORED";

    @SuppressWarnings("all")
    private static final class MyClass {

        final String s0 = EXPOSED; // no explicit expose, serialized by default

        @Expose(serialize = false)
        final String s1 = IGNORED; // ignored by annotation

        @Expose(serialize = true)
        final String s2 = EXPOSED; // serialized by annotation

        final transient String ts0 = IGNORED; // no explicit expose, ignored by default

        @Expose(serialize = false)
        final transient String ts1 = IGNORED; // ignored by annotation

        @Expose(serialize = true)
        final transient String ts2 = EXPOSED; // serialized by annotation

    }

    @Test
    public void test() {
        final Gson gson = new GsonBuilder()
                .addSerializationExclusionStrategy(TransientExclusionStrategy.getInstance())
                .create();
        final JsonObject json = (JsonObject) gson.toJsonTree(new MyClass());
        for ( final Map.Entry<String, JsonElement> e : json.entrySet() ) {
            final String stringValue = e.getValue().getAsString();
            Assertions.assertEquals(EXPOSED, stringValue, () -> "Expected " + EXPOSED + " but was " + stringValue + " for " + e.getKey());
        }
    }

}

Thus you don't need to handle any special type adapters for every such a "special" class or introduce intermediate fields (that's not necessarily not in conflict state with other libraries and frameworks you're using).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM