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;
}
}
Gson
json에 user_id
또는 user_name
키가 포함되지 않은 경우 실패 할 수있는 옵션이 있습니까?
구문 분석 할 값이 적어도 일부 필요하고 다른 값은 선택 사항 일 수있는 경우가 많이있을 수 있습니다.
이 케이스를 전 세계적으로 처리하는 데 사용할 패턴이나 라이브러리가 있습니까?
감사.
아시다시피 Gson에는 "필수 필드"를 정의 할 수있는 기능이 없으며 null
JSON에서 누락 된 항목이 있으면 역 직렬화 된 객체를 얻게 됩니다.
다음은이를 수행하는 재사용 가능한 역 직렬화 기와 주석입니다. 제한 사항은 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 문자열을 구문 분석합니다. 결과에서 누락 된 속성 pojo
은 null
값을 갖습니다.
T pojo = new Gson().fromJson(je, type);
그런 다음 구문 분석 된 모든 필드의 재귀 검사 pojo
가 시작됩니다.
checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);
그런 다음 pojo
의 슈퍼 클래스의 모든 필드도 확인합니다 .
checkSuperClasses(pojo);
일부가 SimpleModel
확장 될 때 필요 하며 필수 SimpleParentModel
로 SimpleModel
표시된 모든 속성이의 속성으로 제공 되는지 확인하고 싶습니다 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.
- Create @Optional annotation
- Mark First Optional. Rest are assumed optional. Earlier are assumed required.
- 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
'programing' 카테고리의 다른 글
100 % CPU를 사용하는 W3WP.EXE-어디서부터 시작해야합니까? (0) | 2021.01.15 |
---|---|
SQL Server에서 행 수준 잠금을 강제 할 수 있습니까? (0) | 2021.01.15 |
VirtualBox의 Ubuntu에서 인터넷 액세스 (0) | 2021.01.15 |
DOM 이벤트 우선 순위 (0) | 2021.01.15 |
C ++ 11에서 쌍 범위 액세스가 제거 된 이유는 무엇입니까? (0) | 2021.01.15 |