How To Serialize A Json Object Child Into A Field?
Solution 1:
As an alternative approach you could also create your own type adapter in order to apply JSON expressions to the not existing fields. It could be based on JsonPath if you are free to add new libraries to the project you're working on.
Having such a non-standard type adapter, you could omit an intermediate mapping class binding directly to the missing field:
finalclassFoo {
// or @JsonPathExpression("foo.echo")@JsonPathExpression("$.foo.echo")
String foo2;
}
@JsonPathExpression
is a custom annotation and it can be processed yourself (JsonPath
could be a shorter name but it's already occupied by the JsonPath library so not to make confusions):
@Retention(RUNTIME)@Target(FIELD)@interface JsonPathExpression {
String value();
}
Type adapters allow to write complicated serialization/deserialization strategies, and one of their features is that they can be combined to write post-processors, so, for example, custom annotations could be processed.
finalclassJsonPathTypeAdapterFactoryimplementsTypeAdapterFactory {
// The type adapter factory is stateless so it can be instantiated onceprivatestaticfinalTypeAdapterFactoryjsonPathTypeAdapterFactory=newJsonPathTypeAdapterFactory();
privateJsonPathTypeAdapterFactory() {
}
static TypeAdapterFactory getJsonPathTypeAdapterFactory() {
return jsonPathTypeAdapterFactory;
}
@Overridepublic <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Pick up the down stream type adapter to avoid infinite recursionfinal TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
// Collect @JsonPathExpression-annotated fieldsfinal Collection<FieldInfo> fieldInfos = FieldInfo.of(typeToken.getRawType());
// If no such fields found, then just return the delegated type adapter// Otherwise wrap the type adapter in order to make some annotation processingreturn fieldInfos.isEmpty()
? delegateAdapter
: newJsonPathTypeAdapter<>(gson, delegateAdapter, gson.getAdapter(JsonElement.class), fieldInfos);
}
privatestaticfinalclassJsonPathTypeAdapter<T>
extendsTypeAdapter<T> {
privatefinal Gson gson;
privatefinal TypeAdapter<T> delegateAdapter;
privatefinal TypeAdapter<JsonElement> jsonElementTypeAdapter;
privatefinal Collection<FieldInfo> fieldInfos;
privateJsonPathTypeAdapter(final Gson gson, final TypeAdapter<T> delegateAdapter, final TypeAdapter<JsonElement> jsonElementTypeAdapter,
final Collection<FieldInfo> fieldInfos) {
this.gson = gson;
this.delegateAdapter = delegateAdapter;
this.jsonElementTypeAdapter = jsonElementTypeAdapter;
this.fieldInfos = fieldInfos;
}
@Overridepublicvoidwrite(final JsonWriter out, final T value)throws IOException {
// JsonPath can only read by expression, but not write by expression, so we can only write it as it is...
delegateAdapter.write(out, value);
}
@Overridepublic T read(final JsonReader in)throws IOException {
// Building the original JSON tree to keep *all* fieldsfinalJsonElementouterJsonElement= jsonElementTypeAdapter.read(in).getAsJsonObject();
// Deserialize the value, not-existing fields will be omittedfinalTvalue= delegateAdapter.fromJsonTree(outerJsonElement);
for ( final FieldInfo fieldInfo : fieldInfos ) {
try {
// Resolving JSON element by a JSON path expressionfinalJsonElementinnerJsonElement= fieldInfo.jsonPath.read(outerJsonElement);
// And convert it to the field typefinalObjectinnerValue= gson.fromJson(innerJsonElement, fieldInfo.field.getType());
// Since now it's what can be assigned to the object field...
fieldInfo.field.set(value, innerValue);
} catch ( final PathNotFoundException ignored ) {
// if no path given, then just ignore the assignment to the field
} catch ( final IllegalAccessException ex ) {
thrownewIOException(ex);
}
}
return value;
}
}
privatestaticfinalclassFieldInfo {
privatefinal Field field;
privatefinal JsonPath jsonPath;
privateFieldInfo(final Field field, final JsonPath jsonPath) {
this.field = field;
this.jsonPath = jsonPath;
}
// Scan the given class for the JsonPathExpressionAnnotationprivatestatic Collection<FieldInfo> of(final Class<?> clazz) {
Collection<FieldInfo> collection = emptyList();
for ( final Field field : clazz.getDeclaredFields() ) {
finalJsonPathExpressionjsonPathExpression= field.getAnnotation(JsonPathExpression.class);
if ( jsonPathExpression != null ) {
if ( collection.isEmpty() ) {
collection = newArrayList<>();
}
field.setAccessible(true);
collection.add(newFieldInfo(field, compile(jsonPathExpression.value())));
}
}
return collection;
}
}
}
Now both Gson and JsonPath must be configured (the latter does not use Gson by default):
privatestaticfinalGsongson=newGsonBuilder()
.registerTypeAdapterFactory(getJsonPathTypeAdapterFactory())
.create();
static {
finalJsonProviderjsonProvider=newGsonJsonProvider(gson);
finalMappingProvidergsonMappingProvider=newGsonMappingProvider(gson);
Configuration.setDefaults(newConfiguration.Defaults() {
@Overridepublic JsonProvider jsonProvider() {
return jsonProvider;
}
@Overridepublic MappingProvider mappingProvider() {
return gsonMappingProvider;
}
@Overridepublic Set<Option> options() {
return EnumSet.noneOf(Option.class);
}
});
}
And how it's used:
finalFoofoo= gson.fromJson("{\"foo\":{\"bar\":\"bar\",\"echo\":\"echo\"}}", Foo.class);
System.out.println(foo.foo2);
finalStringjson= gson.toJson(foo);
System.out.println(json);
Output:
echo {"foo2":"echo"}
Note that this approach has two disadvantages:
- It cannot be used to write the original JSON back due to fundamental reasons like destroying the original information and JsonPath read-only semantics.
- If a given JSON document contains an object property that's already mapped by the same-name object field, the downstream parser (used by the type adapter above) has higher priority therefore causing JSON parsing errors.
Solution 2:
I assume that you use GSON. Make different class for JSONObject.
publicclassFooModel{
@SerializedName("foo")public Foo foo;
publicclassFoo{
@SerializedName("bar")public String Bar;
@SerializedName("echo")public String Echo;
}
}
Solution 3:
Unfortunately you cannot do it using @SerializedName
since it's used in streamed parsing therefore Gson cannot make any look-aheads to resolve path expressions. However, the idea would be nice, but it would require at the least a subtree to be stored in memory that can be too memory-consuming in some cases. Since JsonSerializer
and JsonDeserializer
work with JSON trees only, you can easily write your own JSON deserializer that can just omit unnecessary JSON objects (semantically equivalent to the expression you want to have in @SerializedName
). So,
// To unwrap the top-most JSON objectfinalclassWrapper {
Foo foo;
}
finalclassFoo {
String foo2;
}
The deserializer may be implemented like this (however you should keep in mind that JsonSerializer
and JsonDeserializer
do not play with a Gson built-in ReflectiveTypeAdapterFactory
that actually can process @SerializedName
):
finalclassFooJsonDeserializerimplementsJsonDeserializer<Foo> {
privatestaticfinal JsonDeserializer<Foo> fooJsonDeserializer = newFooJsonDeserializer();
privateFooJsonDeserializer() {
}
static JsonDeserializer<Foo> getFooJsonDeserializer() {
return fooJsonDeserializer;
}
@Overridepublic Foo deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
finalJsonObjectjsonObject= jsonElement.getAsJsonObject();
finalFoofoo=newFoo();
foo.foo2 = jsonObject.get("echo").getAsString();
return foo;
}
}
Example use:
privatestaticfinalGsongson=newGsonBuilder()
.registerTypeAdapter(Foo.class, getFooJsonDeserializer())
.create();
publicstaticvoidmain(final String... args) {
finalWrapperwrapper= gson.fromJson("{\"foo\":{\"bar\":\"bar\",\"echo\":\"echo\"}}", Wrapper.class);
System.out.println(wrapper.foo.foo2);
}
Output:
echo
Solution 4:
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
publicclassExample {
@SerializedName("foo")
@ExposeprivateFoo foo;
publicFoogetFoo() {
return foo;
}
publicvoidsetFoo(Foo foo) {
this.foo = foo;
}
}
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
publicclassFoo {
@SerializedName("bar")
@ExposeprivateString bar;
@SerializedName("echo")
@ExposeprivateString echo;
publicStringgetBar() {
return bar;
}
publicvoidsetBar(String bar) {
this.bar = bar;
}
publicStringgetEcho() {
return echo;
}
publicvoidsetEcho(String echo) {
this.echo = echo;
}
}
you can find more details here
Solution 5:
Yes you can do this
Add this import
import com.google.gson.annotations.SerializedName;
and declare the variable like this
@SerializedName("echo")private String myCustomeName;
Post a Comment for "How To Serialize A Json Object Child Into A Field?"