Retrofit+RxJava 실전 로그(4) - Gson 빈 문자열 문제 해결

9766 단어 androidgsonretrofit
내가 하는 프로젝트에서 서버는 항상 빈 문자열 '' 을 반환 결과로 빈 값을 표시하지만 이것은 Gson에서 문제가 발생할 수 있다. 만약에 이 데이터의 유형이 문자열이 아니라면 Gson 해석은 오류 보고를 할 수 있다. 우리는 프로그램이 자동으로 빈 문자열을 대응하는 유형의 빈 값으로 해석하기를 바란다. 예를 들어 정형은 0으로 해석하고 List형은 하나의 Empty List로 해석하기를 바란다.
이 문제는 내가 Retrofit+Gson을 사용한 이래 가장 큰 구덩이라고 할 수 있다. 그래서 나는 연구 시차가 많지 않아서 원본 코드를 다 봐야 한다.
기괴한 것은 Gson이 빈 문자열을 정형으로 해석할 때'Inavalid double'라는 오류를 보고한 것이다. 원본 코드를 연구한 결과 Gson은 정형으로 해석하는 것을 우선적으로 시도한다. 해석 실패는 오류를 보고하지 않는다. 계속해서 double형으로 해석을 시도한 다음에 실패하면 오류를 보고한다. 그래서'Inavalid double'를 얻었다.
해결 방안: 정형에 대한 해석을 위해 먼저 해석 어댑터를 쓰고 JsonSerializer<Integer>, JsonDeserializer<Integer> 해석 방법을 다시 쓰기를 실현한다. 먼저 String 형식 해석을 시험적으로 사용하고 빈 문자열''과 같으면 0값을 되돌려준다. 그렇지 않으면 정형 해석을 시도하고catch 디지털 형식 이상은 Json 해석 이상 던지기로 전환한다.
public class IntegerDefault0Adapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
    @Override
    public Integer deserialize(JsonElement json, Type typeOfT, 
                            JsonDeserializationContext context) 
                             throws JsonParseException {
        try {
            if (json.getAsString().equals("")){
                return 0;
            }
        } catch (Exception ignore){
        }
        try {
            return json.getAsInt();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }

    @Override
    public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}

그리고 GsonBuilder에 어댑터를 형식 Integer와 형식 int에 등록합니다
   public static Gson buildGson() {
       if (gson == null) {
           gson = new GsonBuilder()
                   .setDateFormat("yyyy-MM-dd HH:mm:ss")
                   .registerTypeAdapter(Integer.class, new IntegerDefault0Adapter())
                   .registerTypeAdapter(int.class, new IntegerDefault0Adapter())
                   .create();
       }
       return gson;
   }

Retrofit를 구축할 때 이 사용자 정의 Gson으로 원본을 대체합니다.
Retrofit = new Retrofit.Builder()
                .baseUrl(API_SERVER + "/")
                //  buildGson      Gson
                .addConverterFactory(ResponseConverterFactory.create(buildGson()))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(mOkHttpClient)
                .build();

이렇게 하면 Gson이 정형 해석을 만났을 때 빈 문자열을 0으로 해석할 수 있다
그리고 리스트의 해석 문제를 같은 방식으로 해결하려고 했는데 상황이 이렇게 간단하지 않을 줄은 몰랐어요.List는 성형처럼 기본적인 유형이 아니기 때문에 List 자체가 데이터 분석을 할 때 범주형을 가져야 하기 때문에 구축할 때 집합 안의 데이터 유형을 정할 수 없다.범용형의 데이터 형식을 정하지 않으면 어댑터를 다시 쓰는 것은 실행할 때 만나는 유형에 따라 각각 조작해야 한다. 이것은 Gson의 작업을 다시 하는 것과 다름없다.또한 원본 코드를 연구한 결과 Gson이 List에 대해 하나의 유형으로 해석하는 것이 아니라 초기화할 때 CollectionTypeAdapterFactory를 가지고 있고 JsonArray 유형의 데이터를 만나면 집합 유형의 해석기를 호출한 다음에 집합에 대응하는 데이터 유형을 맞춘다.한마디로 복잡하고 확장이 잘 안 된다.
연구를 통해 제가 찾은 해결 방안은 주석 방식 @JsonAdapter를 통해 대응하는 어댑터를 지정할 수 있습니다. 우선 순위는 기본 CollectionTypeAdapterFactory보다 높고 GsonBuilder를 통해 전송된 어댑터보다 높습니다.그리고 CollectionTypeAdapterFactory를 복사해서 나오고ListTypeAdapterFactory를 바꿔서 나오세요.
/** *              *       @JsonAdapter          CollectionTypeAdapterFactory */
public final class ListTypeAdapterFactory implements TypeAdapterFactory {

    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        Type type = typeToken.getType();

        Class<? super T> rawType = typeToken.getRawType();
        if (!List.class.isAssignableFrom(rawType)) {
            return null;
        }

        Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
        TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));

        @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
        TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter);
        return result;
    }

    private static final class Adapter<E> extends TypeAdapter<List<E>> {
        private final TypeAdapter<E> elementTypeAdapter;

        public Adapter(Gson context, Type elementType,
                       TypeAdapter<E> elementTypeAdapter) {
            this.elementTypeAdapter = new TypeAdapterRuntimeTypeWrapper<E>(
                                        context, elementTypeAdapter, elementType);
        }

        //       ,      
        public List<E> read(JsonReader in) throws IOException {
            //null   null
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                return null;
            }
            //        
            List<E> list = new ArrayList<>();
            try {
                in.beginArray();
                while (in.hasNext()) {
                    E instance = elementTypeAdapter.read(in);
                    list.add(instance);
                }
                in.endArray();
                //        
            } catch (IllegalStateException e){ //       ,  BEGIN_ARRAY  
                //          ,        ,       
                //       ,      ,           
                if (!"".equals(in.nextString())){
                    throw e;
                }
            }

            return list;
        }

        public void write(JsonWriter out, List<E> list) throws IOException {
            if (list == null) {
                out.nullValue();
                return;
            }

            out.beginArray();
            for (E element : list) {
                elementTypeAdapter.write(out, element);
            }
            out.endArray();
        }
    }
}

마지막으로 모델 클래스에서 메모 지정
public class UserListModel{
    @JsonAdapter(ListTypeAdapterFactory.class)
    List<UserProfileModel> users;
}

이렇게 하면 Gson은 빈 문자열을 빈 목록으로 해석할 수 있다.

좋은 웹페이지 즐겨찾기