programing

Gson 선택 및 필수 필드

sourcetip 2021. 1. 15. 20:24
반응형

Gson 선택 및 필수 필드


Gson필수 필드와 선택 필드를 어떻게 처리해야 합니까?

모든 필드는 선택 사항이므로 응답 json에 키가 포함되어 있는지 여부에 따라 네트워크 요청을 실제로 실패 할 수 없으며 Gson단순히 null로 구문 분석합니다.

내가 사용하는 방법 gson.fromJson(json, mClassOfT);

예를 들어 다음 json이있는 경우 :

{"user_id":128591, "user_name":"TestUser"}

그리고 내 수업 :

public class User {

    @SerializedName("user_id")
    private String mId;

    @SerializedName("user_name")
    private String mName;

    public String getId() {
        return mId;
    }

    public void setId(String id) {
        mId = id;
    }

    public String getName() {
        return mName;
    }

    public void setName(String name) {
        mName = name;
    }
}

Gsonjson에 user_id또는 user_name키가 포함되지 않은 경우 실패 할 수있는 옵션이 있습니까?

구문 분석 할 값이 적어도 일부 필요하고 다른 값은 선택 사항 일 수있는 경우가 많이있을 수 있습니다.

이 케이스를 전 세계적으로 처리하는 데 사용할 패턴이나 라이브러리가 있습니까?

감사.


아시다시피 Gson에는 "필수 필드"를 정의 할 수있는 기능이 없으며 nullJSON에서 누락 된 항목이 있으면 역 직렬화 된 객체를 얻게 됩니다.

다음은이를 수행하는 재사용 가능한 역 직렬화 기와 주석입니다. 제한 사항은 POJO에 사용자 지정 deserializer가있는 그대로 필요한 경우 조금 더 나아가 Gson생성자 에있는 개체를 전달 하여 개체 자체로 역 직렬화하거나 주석 체크 아웃을 별도의 메서드로 이동하고 사용해야한다는 것입니다. deserializer에 있습니다. 또한 고유 한 예외를 생성하여 예외 처리를 개선 하고 호출자 JsonParseException를 통해 감지 할 수 있도록에 전달할 수 있습니다 getCause().

즉, 대부분의 경우 다음과 같이 작동합니다.

public class App
{

    public static void main(String[] args)
    {
        Gson gson =
            new GsonBuilder()
            .registerTypeAdapter(TestAnnotationBean.class, new AnnotatedDeserializer<TestAnnotationBean>())
            .create();

        String json = "{\"foo\":\"This is foo\",\"bar\":\"this is bar\"}";
        TestAnnotationBean tab = gson.fromJson(json, TestAnnotationBean.class);
        System.out.println(tab.foo);
        System.out.println(tab.bar);

        json = "{\"foo\":\"This is foo\"}";
        tab = gson.fromJson(json, TestAnnotationBean.class);
        System.out.println(tab.foo);
        System.out.println(tab.bar);

        json = "{\"bar\":\"This is bar\"}";
        tab = gson.fromJson(json, TestAnnotationBean.class);
        System.out.println(tab.foo);
        System.out.println(tab.bar);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface JsonRequired
{
}

class TestAnnotationBean
{
    @JsonRequired public String foo;
    public String bar;
}

class AnnotatedDeserializer<T> implements JsonDeserializer<T>
{

    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
    {
        T pojo = new Gson().fromJson(je, type);

        Field[] fields = pojo.getClass().getDeclaredFields();
        for (Field f : fields)
        {
            if (f.getAnnotation(JsonRequired.class) != null)
            {
                try
                {
                    f.setAccessible(true);
                    if (f.get(pojo) == null)
                    {
                        throw new JsonParseException("Missing field in JSON: " + f.getName());
                    }
                }
                catch (IllegalArgumentException ex)
                {
                    Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
                }
                catch (IllegalAccessException ex)
                {
                    Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        return pojo;

    }
}

산출:

이것은 foo입니다
이것은 바
이것은 foo입니다
없는
스레드 "main"의 예외 com.google.gson.JsonParseException : JSON에 필드 누락 : foo

Brian Roach의 답변은 매우 좋지만 때로는 다음을 처리해야합니다.

  • 모델의 슈퍼 클래스 속성
  • 배열 내부의 속성

이러한 목적을 위해 다음 클래스를 사용할 수 있습니다.

/**
 * Adds the feature to use required fields in models.
 *
 * @param <T> Model to parse to.
 */
public class JsonDeserializerWithOptions<T> implements JsonDeserializer<T> {

    /**
     * To mark required fields of the model:
     * json parsing will be failed if these fields won't be provided.
     * */
    @Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
    @Target(ElementType.FIELD)          // to make annotation accessible through reflection
    public @interface FieldRequired {}

    /**
     * Called when the model is being parsed.
     *
     * @param je   Source json string.
     * @param type Object's model.
     * @param jdc  Unused in this case.
     *
     * @return Parsed object.
     *
     * @throws JsonParseException When parsing is impossible.
     * */
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        // Parsing object as usual.
        T pojo = new Gson().fromJson(je, type);

        // Getting all fields of the class and checking if all required ones were provided.
        checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);

        // Checking if all required fields of parent classes were provided.
        checkSuperClasses(pojo);

        // All checks are ok.
        return pojo;
    }

    /**
     * Checks whether all required fields were provided in the class.
     *
     * @param fields Fields to be checked.
     * @param pojo   Instance to check fields in.
     *
     * @throws JsonParseException When some required field was not met.
     * */
    private void checkRequiredFields(@NonNull Field[] fields, @NonNull Object pojo)
            throws JsonParseException {
        // Checking nested list items too.
        if (pojo instanceof List) {
            final List pojoList = (List) pojo;
            for (final Object pojoListPojo : pojoList) {
                checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
                checkSuperClasses(pojoListPojo);
            }
        }

        for (Field f : fields) {
            // If some field has required annotation.
            if (f.getAnnotation(FieldRequired.class) != null) {
                try {
                    // Trying to read this field's value and check that it truly has value.
                    f.setAccessible(true);
                    Object fieldObject = f.get(pojo);
                    if (fieldObject == null) {
                        // Required value is null - throwing error.
                        throw new JsonParseException(String.format("%1$s -> %2$s",
                                pojo.getClass().getSimpleName(),
                                f.getName()));
                    } else {
                        checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
                        checkSuperClasses(fieldObject);
                    }
                }

                // Exceptions while reflection.
                catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new JsonParseException(e);
                }
            }
        }
    }

    /**
     * Checks whether all super classes have all required fields.
     *
     * @param pojo Object to check required fields in its superclasses.
     *
     * @throws JsonParseException When some required field was not met.
     * */
    private void checkSuperClasses(@NonNull Object pojo) throws JsonParseException {
        Class<?> superclass = pojo.getClass();
        while ((superclass = superclass.getSuperclass()) != null) {
            checkRequiredFields(superclass.getDeclaredFields(), pojo);
        }
    }

}

먼저 필수 필드를 표시하는 모든 인터페이스 (주석)가 설명되어 있으며 나중에 그 사용법의 예를 볼 수 있습니다.

    /**
     * To mark required fields of the model:
     * json parsing will be failed if these fields won't be provided.
     * */
    @Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
    @Target(ElementType.FIELD)          // to make annotation accessible throw the reflection
    public @interface FieldRequired {}

그런 다음 deserialize방법이 구현됩니다. 평소와 같이 json 문자열을 구문 분석합니다. 결과에서 누락 된 속성 pojonull값을 갖습니다.

T pojo = new Gson().fromJson(je, type);

그런 다음 구문 분석 된 모든 필드의 재귀 검사 pojo가 시작됩니다.

checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);

그런 다음 pojo의 슈퍼 클래스의 모든 필드도 확인합니다 .

checkSuperClasses(pojo);

일부가 SimpleModel확장 될 때 필요 하며 필수 SimpleParentModelSimpleModel표시된 모든 속성이의 속성으로 제공 되는지 확인하고 싶습니다 SimpleParentModel.

방법을 살펴 보겠습니다 checkRequiredFields. 우선 일부 속성이 List(json 배열)의 인스턴스인지 확인합니다 .이 경우 목록의 모든 개체도 모든 필수 필드가 제공되었는지 확인해야합니다.

if (pojo instanceof List) {
    final List pojoList = (List) pojo;
    for (final Object pojoListPojo : pojoList) {
        checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
        checkSuperClasses(pojoListPojo);
    }
}

그런 다음의 모든 필드를 반복 pojo하여 FieldRequired주석이있는 모든 필드 가 제공 되었는지 확인합니다 (이 필드가 null이 아님을 의미 함). 필요한 null 속성이 발생하면 예외가 발생합니다. 그렇지 않으면 현재 필드에 대해 유효성 검사의 또 다른 재귀 단계가 시작되고 필드의 부모 클래스 속성도 확인됩니다.

        for (Field f : fields) {
            // If some field has required annotation.
            if (f.getAnnotation(FieldRequired.class) != null) {
                try {
                    // Trying to read this field's value and check that it truly has value.
                    f.setAccessible(true);
                    Object fieldObject = f.get(pojo);
                    if (fieldObject == null) {
                        // Required value is null - throwing error.
                        throw new JsonParseException(String.format("%1$s -> %2$s",
                                pojo.getClass().getSimpleName(),
                                f.getName()));
                    } else {
                        checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
                        checkSuperClasses(fieldObject);
                    }
                }

                // Exceptions while reflection.
                catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new JsonParseException(e);
                }
            }
        }

마지막으로 검토해야 할 방법은 checkSuperClasses다음과 pojo같습니다.의 슈퍼 클래스의 속성을 확인하는 유사한 필수 필드 유효성 검사를 실행합니다 .

    Class<?> superclass = pojo.getClass();
    while ((superclass = superclass.getSuperclass()) != null) {
        checkRequiredFields(superclass.getDeclaredFields(), pojo);
    }

마지막으로이 JsonDeserializerWithOptions사용법 의 몇 가지 예를 살펴 보겠습니다 . 다음 모델이 있다고 가정합니다.

private class SimpleModel extends SimpleParentModel {

    @JsonDeserializerWithOptions.FieldRequired Long id;
    @JsonDeserializerWithOptions.FieldRequired NestedModel nested;
    @JsonDeserializerWithOptions.FieldRequired ArrayList<ListModel> list;

}

private class SimpleParentModel {

    @JsonDeserializerWithOptions.FieldRequired Integer rev;

}

private class NestedModel extends NestedParentModel {

    @JsonDeserializerWithOptions.FieldRequired Long id;

}

private class NestedParentModel {

    @JsonDeserializerWithOptions.FieldRequired Integer rev;

}

private class ListModel {

    @JsonDeserializerWithOptions.FieldRequired Long id;

}

다음 SimpleModel과 같이 예외없이 올바르게 구문 분석 될 수 있습니다.

final Gson gson = new GsonBuilder()
    .registerTypeAdapter(SimpleModel.class, new JsonDeserializerWithOptions<SimpleModel>())
    .create();

gson.fromJson("{\"list\":[ { \"id\":1 } ], \"id\":1, \"rev\":22, \"nested\": { \"id\":2, \"rev\":2 }}", SimpleModel.class);

물론 제공된 솔루션을 개선하고 더 많은 기능을 수용 할 수 있습니다. 예를 들어 FieldRequired주석으로 표시되지 않은 중첩 된 객체에 대한 유효성 검사가 있습니다. 현재는 답변 범위를 벗어 났지만 나중에 추가 할 수 있습니다.


나는 많이 검색했지만 좋은 답을 찾지 못했습니다. 내가 선택한 솔루션은 다음과 같습니다.

JSON에서 설정해야하는 모든 필드는 개체, 즉 boxed Integer, Boolean 등입니다. 그런 다음 리플렉션을 사용하여 필드가 null이 아닌지 확인할 수 있습니다.

public class CJSONSerializable {
    public void checkDeserialization() throws IllegalAccessException, JsonParseException {
        for (Field f : getClass().getDeclaredFields()) {
            if (f.get(this) == null) {
                throw new JsonParseException("Field " + f.getName() + " was not initialized.");
            }
        }
    }
}

이 클래스에서 JSON 객체를 파생시킬 수 있습니다.

public class CJSONResp extends CJSONSerializable {
  @SerializedName("Status")
  public String status;

  @SerializedName("Content-Type")
  public String contentType;
}

그런 다음 GSON으로 구문 분석 한 후 checkDeserialization을 호출 할 수 있으며 필드 중 일부가 null이면보고합니다.


This is my simple solution that creates a generic solution with minimum coding.

  1. Create @Optional annotation
  2. Mark First Optional. Rest are assumed optional. Earlier are assumed required.
  3. Create a generic 'loader' method that checks that source Json object has a value. The loop stops once an @Optional field is encountered.

I am using subclassing so the grunt work is done in the superclass.

Here is the superclass code.

import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
... 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Optional {
  public boolean enabled() default true;
}

and the grunt work method

@SuppressWarnings ("unchecked")
  public <T> T payload(JsonObject oJR,Class<T> T) throws Exception {
  StringBuilder oSB = new StringBuilder();
  String sSep = "";
  Object o = gson.fromJson(oJR,T);
  // Ensure all fields are populated until we reach @Optional
  Field[] oFlds =  T.getDeclaredFields();
  for(Field oFld:oFlds) {
    Annotation oAnno = oFld.getAnnotation(Optional.class);
    if (oAnno != null) break;
    if (!oJR.has(oFld.getName())) {
      oSB.append(sSep+oFld.getName());
      sSep = ",";
    }
  }
  if (oSB.length() > 0) throw CVT.e("Required fields "+oSB+" mising");
  return (T)o;
}

and an example of usage

public static class Payload {
  String sUserType ;
  String sUserID   ;
  String sSecpw    ;
  @Optional
  String sUserDev  ;
  String sUserMark ;
}

and the populating code

Payload oPL = payload(oJR,Payload.class);

In this case sUserDev and sUserMark are optional and the rest required. The solution relies on the fact that the class stores the Field definitions in the declared order.

ReferenceURL : https://stackoverflow.com/questions/21626690/gson-optional-and-required-fields

반응형