Skip to content

Commit

Permalink
#764 Gson.Other for other/unknown fields
Browse files Browse the repository at this point in the history
  • Loading branch information
elucash committed May 19, 2018
1 parent 4f3aaa1 commit 28f97c0
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 8 deletions.
9 changes: 9 additions & 0 deletions gson/src/org/immutables/gson/Gson.java
Expand Up @@ -18,6 +18,7 @@
import com.google.gson.FieldNamingPolicy; import com.google.gson.FieldNamingPolicy;
import com.google.gson.FieldNamingStrategy; import com.google.gson.FieldNamingStrategy;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
Expand Down Expand Up @@ -175,4 +176,12 @@
@Documented @Documented
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface Ignore {} public @interface Ignore {}

/**
* Can be put on accessor of type {@link JsonObject} as catch all collection of all other fields,
* not mapped to declared attributes (i.e. unknown).
*/
@Documented
@Target(ElementType.METHOD)
public @interface Other {}
} }
14 changes: 14 additions & 0 deletions gson/test/org/immutables/gson/adapter/GsonFeaturesTest.java
Expand Up @@ -20,7 +20,9 @@
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.Test; import org.junit.Test;
Expand All @@ -32,6 +34,7 @@ public class GsonFeaturesTest {
.serializeNulls() .serializeNulls()
.registerTypeAdapterFactory(new GsonAdaptersSimple()) .registerTypeAdapterFactory(new GsonAdaptersSimple())
.registerTypeAdapterFactory(new GsonAdaptersUnsimple()) .registerTypeAdapterFactory(new GsonAdaptersUnsimple())
.registerTypeAdapterFactory(new GsonAdaptersOtherAttributes())
.create(); .create();


final Gson gsonDefault = new GsonBuilder() final Gson gsonDefault = new GsonBuilder()
Expand Down Expand Up @@ -123,6 +126,17 @@ public void nullableContainersNames() {
check(json).contains("\"m\":"); check(json).contains("\"m\":");
} }


@Test
public void otherAttributes() {
String json = "{\"a\":1,\"b\":\"B\",\"c\":true,\"d\":null}";
OtherAttributes o = gsonWithOptions.fromJson(json, OtherAttributes.class);

check(o.rest().get("c")).is(new JsonPrimitive(true));
check(o.rest().get("d")).is(JsonNull.INSTANCE);

check(gsonWithOptions.toJson(o)).is(json);
}

private Set<String> keysIn(JsonObject json) { private Set<String> keysIn(JsonObject json) {
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
for (Map.Entry<String, JsonElement> entry : json.entrySet()) { for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
Expand Down
16 changes: 16 additions & 0 deletions gson/test/org/immutables/gson/adapter/OtherAttributes.java
@@ -0,0 +1,16 @@
package org.immutables.gson.adapter;

import com.google.gson.JsonObject;
import org.immutables.gson.Gson;
import org.immutables.value.Value;

@Gson.TypeAdapters
@Value.Immutable
public interface OtherAttributes {
int a();

String b();

@Gson.Other
JsonObject rest();
}
34 changes: 32 additions & 2 deletions value-processor/src/org/immutables/value/processor/Gsons.generator
Expand Up @@ -141,6 +141,9 @@ private static class [type.name]TypeAdapter[type.generics] extends TypeAdapter<[
public final [a.secondaryElementType] [a.name]SecondaryTypeSample = null; public final [a.secondaryElementType] [a.name]SecondaryTypeSample = null;
[/if] [/if]
[/for] [/for]
[for other = type.gsonOther][if other]
private final TypeAdapter<JsonElement> [other.name]TypeAdapter;
[/if][/for]
[for a in allAttributes] [for a in allAttributes]
[if a.requiresMarshalingAdapter and a.primitiveArrayType] [if a.requiresMarshalingAdapter and a.primitiveArrayType]
private final TypeAdapter<[a.type]> [a.name]TypeAdapter; private final TypeAdapter<[a.type]> [a.name]TypeAdapter;
Expand Down Expand Up @@ -171,6 +174,9 @@ private static class [type.name]TypeAdapter[type.generics] extends TypeAdapter<[
[if type.generics] [if type.generics]
java.lang.reflect.Type['[]'] typeArguments = getTypeArguments(type); java.lang.reflect.Type['[]'] typeArguments = getTypeArguments(type);
[/if] [/if]
[for other = type.gsonOther][if other]
this.[other.name]TypeAdapter = gson.getAdapter(JsonElement.class);
[/if][/for]
[for a in allAttributes] [for a in allAttributes]
[if a.requiresMarshalingAdapter] [if a.requiresMarshalingAdapter]
[getAdapterFromGson a false] [getAdapterFromGson a false]
Expand Down Expand Up @@ -259,11 +265,21 @@ private void write[type.name](JsonWriter out, [type.typeAbstract] instance)
[for a in type.marshaledAttributes] [for a in type.marshaledAttributes]
[generateMarshalAttributeValue type a] [generateMarshalAttributeValue type a]
[/for] [/for]
[generateMarshalOther type]
out.endObject(); out.endObject();
[/if] [/if]
} }
[/template] [/template]


[template generateMarshalOther Type type]
[for other = type.gsonOther][if other]
for (java.util.Map.Entry<String, JsonElement> other : instance.[other.names.get]().entrySet()) {
out.name(other.getKey());
[other.name]TypeAdapter.write(out, other.getValue());
}
[/if][/for]
[/template]

[template generateMarshalConstructorValue Type type Attribute a] [template generateMarshalConstructorValue Type type Attribute a]
[if a.optionalType] [if a.optionalType]
[a.type] [a.name]Optional = instance.[a.names.get](); [a.type] [a.name]Optional = instance.[a.names.get]();
Expand Down Expand Up @@ -514,19 +530,27 @@ private [if type.generics.empty][if not type.unmarshaledAttributes]static[/if][/
} }
[type.typeBuilder] builder = [castBuildStagedBuilder type][type.factoryBuilder]()[/castBuildStagedBuilder]; [type.typeBuilder] builder = [castBuildStagedBuilder type][type.factoryBuilder]()[/castBuildStagedBuilder];
[if type.unmarshaledAttributes] [if type.unmarshaledAttributes]
[for other = type.gsonOther]
[if other]
JsonObject other = new JsonObject();
[/if]
in.beginObject(); in.beginObject();
while (in.hasNext()) { while (in.hasNext()) {
eachAttribute(in, builder); eachAttribute(in, builder[if other], other[/if]);
} }
in.endObject(); in.endObject();
[if other]
builder.[other.names.init](other);
[/if]
[/for]
[else] [else]
in.skipValue(); in.skipValue();
[/if] [/if]
return builder.[type.names.build](); return builder.[type.names.build]();
} }
[if type.unmarshaledAttributes] [if type.unmarshaledAttributes]


private void eachAttribute(JsonReader in, [type.typeBuilder] builder) private void eachAttribute(JsonReader in, [type.typeBuilder] builder[if type.gsonOther], JsonObject other[/if])
throws IOException { throws IOException {
String attributeName = in.nextName(); String attributeName = in.nextName();
[if type.gsonTypeAdapters.fieldNamingStrategy] [if type.gsonTypeAdapters.fieldNamingStrategy]
Expand Down Expand Up @@ -558,7 +582,13 @@ private void eachAttribute(JsonReader in, [type.typeBuilder] builder)
default: default:
} }
[/if] [/if]
[for other = type.gsonOther]
[if other]
other.add(attributeName, [other.name]TypeAdapter.read(in));
[else]
in.skipValue(); in.skipValue();
[/if]
[/for]
} }
[/if] [/if]
[for s in type.unmarshaledAttributes] [for s in type.unmarshaledAttributes]
Expand Down
Expand Up @@ -20,6 +20,8 @@
public final class GsonMirrors { public final class GsonMirrors {
private GsonMirrors() {} private GsonMirrors() {}


public static String JSON_OBJECT_TYPE = "com.google.gson.JsonObject";

@Mirror.Annotation("org.immutables.gson.Gson.TypeAdapters") @Mirror.Annotation("org.immutables.gson.Gson.TypeAdapters")
public @interface TypeAdapters { public @interface TypeAdapters {
boolean metainfService() default true; boolean metainfService() default true;
Expand Down Expand Up @@ -50,4 +52,7 @@ private GsonMirrors() {}


@Mirror.Annotation("org.immutables.gson.Gson.Ignore") @Mirror.Annotation("org.immutables.gson.Gson.Ignore")
public @interface Ignore {} public @interface Ignore {}

@Mirror.Annotation("org.immutables.gson.Gson.Other")
public @interface GsonOther {}
} }
Expand Up @@ -289,6 +289,26 @@ public String getMarshaledName() {
return names.raw; return names.raw;
} }


private @Nullable Boolean isGsonOther = null;

public boolean isGsonOther() {
if (isGsonOther == null) {
if (GsonOtherMirror.isPresent(element)) {
if (!isGenerateAbstract || !rawTypeName.equals(GsonMirrors.JSON_OBJECT_TYPE)) {
report().error(
"@Gson.Other attribute must be abstract accessor of type %s",
GsonMirrors.JSON_OBJECT_TYPE);
isGsonOther = false;
} else {
isGsonOther = true;
}
} else {
isGsonOther = false;
}
}
return isGsonOther;
}

public boolean isForcedEmpty() { public boolean isForcedEmpty() {
return !containingType.gsonTypeAdapters().emptyAsNulls(); return !containingType.gsonTypeAdapters().emptyAsNulls();
} }
Expand Down
Expand Up @@ -877,19 +877,19 @@ private FluentIterable<ValueAttribute> attributes() {


public List<ValueAttribute> getMarshaledAttributes() { public List<ValueAttribute> getMarshaledAttributes() {
ImmutableList.Builder<ValueAttribute> builder = ImmutableList.builder(); ImmutableList.Builder<ValueAttribute> builder = ImmutableList.builder();
for (ValueAttribute attribute : getImplementedAttributes()) { for (ValueAttribute a : getImplementedAttributes()) {
if (!attribute.isJsonIgnore()) { if (!a.isJsonIgnore() && !a.isGsonOther()) {
builder.add(attribute); builder.add(a);
} }
} }
return builder.build(); return builder.build();
} }


public List<ValueAttribute> getUnmarshaledAttributes() { public List<ValueAttribute> getUnmarshaledAttributes() {
ImmutableList.Builder<ValueAttribute> builder = ImmutableList.builder(); ImmutableList.Builder<ValueAttribute> builder = ImmutableList.builder();
for (ValueAttribute attribute : getSettableAttributes()) { for (ValueAttribute a : getSettableAttributes()) {
if (!attribute.isJsonIgnore()) { if (!a.isJsonIgnore() && !a.isGsonOther()) {
builder.add(attribute); builder.add(a);
} }
} }
return builder.build(); return builder.build();
Expand Down Expand Up @@ -1697,6 +1697,15 @@ public GsonTypeTokens getGsonTypeTokens() {
} }
return gsonTypeTokens; return gsonTypeTokens;
} }

public @Nullable ValueAttribute getGsonOther() {
for (ValueAttribute a : attributes) {
if (a.isGsonOther()) {
return a;
}
}
return null;
}


private @Nullable TypeExtractor typeExtractor; private @Nullable TypeExtractor typeExtractor;


Expand Down

0 comments on commit 28f97c0

Please sign in to comment.