How to serialize a JSON object child into a field?

前端 未结 6 838
一生所求
一生所求 2021-01-19 10:22

I have a JSON object that looks like this

{
  \"foo\":{
      \"bar\":\"bar\",
      \"echo\":\"echo\"
  }
}

But then my Java object looks

相关标签:
6条回答
  • 2021-01-19 10:41
    import com.google.gson.annotations.Expose;
    import com.google.gson.annotations.SerializedName;
    
    public class Example {
    
    @SerializedName("foo")
    @Expose
    private Foo foo;
    
    public Foo getFoo() {
    return foo;
    }
    
    public void setFoo(Foo foo) {
    this.foo = foo;
    }
    
    }
    
    import com.google.gson.annotations.Expose;
    import com.google.gson.annotations.SerializedName;
    
    public class Foo {
    
    @SerializedName("bar")
    @Expose
    private String bar;
    @SerializedName("echo")
    @Expose
    private String echo;
    
    public String getBar() {
    return bar;
    }
    
    public void setBar(String bar) {
    this.bar = bar;
    }
    
    public String getEcho() {
    return echo;
    }
    
    public void setEcho(String echo) {
    this.echo = echo;
    }
    
    }
    

    you can find more details here

    0 讨论(0)
  • 2021-01-19 10:45

    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 object
    final class Wrapper {   
        Foo foo;
    }
    
    final class Foo {   
        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):

    final class FooJsonDeserializer
            implements JsonDeserializer<Foo> {
    
        private static final JsonDeserializer<Foo> fooJsonDeserializer = new FooJsonDeserializer();
    
        private FooJsonDeserializer() {
        }
    
        static JsonDeserializer<Foo> getFooJsonDeserializer() {
            return fooJsonDeserializer;
        }
    
        @Override
        public Foo deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) {
            final JsonObject jsonObject = jsonElement.getAsJsonObject();
            final Foo foo = new Foo();
            foo.foo2 = jsonObject.get("echo").getAsString();
            return foo;
        }
    
    }
    

    Example use:

    private static final Gson gson = new GsonBuilder()
            .registerTypeAdapter(Foo.class, getFooJsonDeserializer())
            .create();
    
    public static void main(final String... args) {
        final Wrapper wrapper = gson.fromJson("{\"foo\":{\"bar\":\"bar\",\"echo\":\"echo\"}}", Wrapper.class);
        System.out.println(wrapper.foo.foo2);
    }
    

    Output:

    echo

    0 讨论(0)
  • 2021-01-19 10:53

    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;
    
    0 讨论(0)
  • 2021-01-19 10:54

    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:

    final class Foo {
    
        // 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.

    final class JsonPathTypeAdapterFactory
            implements TypeAdapterFactory {
    
        // The type adapter factory is stateless so it can be instantiated once
        private static final TypeAdapterFactory jsonPathTypeAdapterFactory = new JsonPathTypeAdapterFactory();
    
        private JsonPathTypeAdapterFactory() {
        }
    
        static TypeAdapterFactory getJsonPathTypeAdapterFactory() {
            return jsonPathTypeAdapterFactory;
        }
    
        @Override
        public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
            // Pick up the down stream type adapter to avoid infinite recursion
            final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
            // Collect @JsonPathExpression-annotated fields
            final 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 processing
            return fieldInfos.isEmpty()
                    ? delegateAdapter
                    : new JsonPathTypeAdapter<>(gson, delegateAdapter, gson.getAdapter(JsonElement.class), fieldInfos);
        }
    
        private static final class JsonPathTypeAdapter<T>
                extends TypeAdapter<T> {
    
            private final Gson gson;
            private final TypeAdapter<T> delegateAdapter;
            private final TypeAdapter<JsonElement> jsonElementTypeAdapter;
            private final Collection<FieldInfo> fieldInfos;
    
            private JsonPathTypeAdapter(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;
            }
    
            @Override
            public void write(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);
            }
    
            @Override
            public T read(final JsonReader in)
                    throws IOException {
                // Building the original JSON tree to keep *all* fields
                final JsonElement outerJsonElement = jsonElementTypeAdapter.read(in).getAsJsonObject();
                // Deserialize the value, not-existing fields will be omitted
                final T value = delegateAdapter.fromJsonTree(outerJsonElement);
                for ( final FieldInfo fieldInfo : fieldInfos ) {
                    try {
                        // Resolving JSON element by a JSON path expression
                        final JsonElement innerJsonElement = fieldInfo.jsonPath.read(outerJsonElement);
                        // And convert it to the field type
                        final Object innerValue = 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 ) {
                        throw new IOException(ex);
                    }
                }
                return value;
            }
    
        }
    
        private static final class FieldInfo {
    
            private final Field field;
            private final JsonPath jsonPath;
    
            private FieldInfo(final Field field, final JsonPath jsonPath) {
                this.field = field;
                this.jsonPath = jsonPath;
            }
    
            // Scan the given class for the JsonPathExpressionAnnotation
            private static Collection<FieldInfo> of(final Class<?> clazz) {
                Collection<FieldInfo> collection = emptyList();
                for ( final Field field : clazz.getDeclaredFields() ) {
                    final JsonPathExpression jsonPathExpression = field.getAnnotation(JsonPathExpression.class);
                    if ( jsonPathExpression != null ) {
                        if ( collection.isEmpty() ) {
                            collection = new ArrayList<>();
                        }
                        field.setAccessible(true);
                        collection.add(new FieldInfo(field, compile(jsonPathExpression.value())));
                    }
                }
                return collection;
            }
    
        }
    
    }
    

    Now both Gson and JsonPath must be configured (the latter does not use Gson by default):

    private static final Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(getJsonPathTypeAdapterFactory())
            .create();
    
    static {
        final JsonProvider jsonProvider = new GsonJsonProvider(gson);
        final MappingProvider gsonMappingProvider = new GsonMappingProvider(gson);
        Configuration.setDefaults(new Configuration.Defaults() {
            @Override
            public JsonProvider jsonProvider() {
                return jsonProvider;
            }
    
            @Override
            public MappingProvider mappingProvider() {
                return gsonMappingProvider;
            }
    
            @Override
            public Set<Option> options() {
                return EnumSet.noneOf(Option.class);
            }
        });
    
    }
    

    And how it's used:

    final Foo foo = gson.fromJson("{\"foo\":{\"bar\":\"bar\",\"echo\":\"echo\"}}", Foo.class);
    System.out.println(foo.foo2);
    final String json = 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.
    0 讨论(0)
  • 2021-01-19 10:58

    I assume that you use GSON. Make different class for JSONObject.

    public class FooModel {
        @SerializedName("foo")
        public Foo foo;
    
        public class Foo {
            @SerializedName("bar")
            public String Bar;
    
            @SerializedName("echo")
            public String Echo;
        }
    }
    
    0 讨论(0)
  • 2021-01-19 11:02

    You have to write a model class with all the variables and then you can use

    Gson gson=new Gson();
    ClassName objectName=gson.fromJson(yourJsonObject,ClassName.class);
    

    Here objectName contains your json

    0 讨论(0)
提交回复
热议问题