2016-12-03 1 views
2

우리가이 JSON 있다고 가정합시다 : Moshi로 일반 유형을 비 직렬화하는 방법은 무엇입니까?

[ 
    { 
    "__typename": "Car", 
    "id": "123", 
    "name": "Toyota Prius", 
    "numDoors": 4 
    }, 
    { 
    "__typename": "Boat", 
    "id": "4567", 
    "name": "U.S.S. Constitution", 
    "propulsion": "SAIL" 
    } 
] 

이 (목록에 더 많은 요소가있을 수를,이 두 표시)

I 공통의 Vehicle 기본 클래스를 사용 CarBoat POJO를이 필드 :

public abstract class Vehicle { 
    public final String id; 
    public final String name; 
} 

public class Car extends Vehicle { 
    public final Integer numDoors; 
} 

public class Boat extends Vehicle { 
    public final String propulsion; 
} 

List<Vehicle>되어야이 JSON 파싱 결과. 문제는 상자 밖에서 __typenameBoatCar과 구별하는 방법임을 아는 JSON 파서가 없다는 것입니다.

는 GSON으로, 나는 적절한 유형에 특정 JSON 개체를 구문 분석 공급 JsonDeserializationContextdeserialize()을 사용 후, 이것은 Car 또는 Boat 여부 확인은 __typename 필드를 검사 할 수있는 JsonDeserializer<Vehicle> 만들 수 있습니다. 이것은 잘 작동합니다.

그러나 플러그 할 수있는 JSON 파서를 지원해야한다는 점을 명심해야하며 모시를 대안 파서로 사용하려고합니다. 그러나이 특정 문제는 현재 Moshi 문서에서 잘 다루지 못하며 해결 방법을 찾는 데 어려움을 겪고 있습니다.

가장 유사한 유사 콘텐츠는 JsonDeserializer<T>is JsonAdapter<T>입니다. 그러나 fromJson()은 파괴적인 API를 가진 JsonReader을 전달합니다. __typename이 무엇인지 알아 보려면 JsonReader 이벤트에서 모든 것을 수동으로 구문 분석 할 수 있어야합니다. 적절한 구체 유형을 알았다면 adapter() on the Moshi instance을 호출하여 기존의 Moshi 구문 분석 논리를 호출 할 수 있지만 JsonReader의 데이터를 소비하고 더 이상 완전한 개체 설명을 제공하지 못했습니다.

JsonDeserializer<Vehicle>의 다른 아날로그는 @FromJson-annotated method이고 Vehicle을 반환합니다. 그러나, 나는 그 방법으로 넘어갈 간단한 것을 식별 할 수 없다. 내가 Moshi와 유형 어댑터로 등록하는 클래스에 @FromJson Vehicle rideLikeTheWind(SemiParsedKindOfVehicle rawVehicle)이있는 경우, 이론적으로

public class SemiParsedKindOfVehicle { 
    public final String id; 
    public final String name; 
    public final Integer numDoors; 
    public final String propulsion; 
    public final String __typename; 
} 

다음 : 내가 생각할 수있는 유일한 방법은 가능한 모든 필드의 집합을 나타내는 또 다른 POJO를 만드는 것입니다 Moshi가 JSON 객체를 SemiParsedKindOfVehicle 인스턴스로 구문 분석하고 rideLikeTheWind()으로 전화를 걸 수 있습니다. 거기에서 __typename을 찾아 유형을 식별하고 Car 또는 Boat을 직접 작성하여 해당 오브젝트를 리턴합니다.

doable의 경우 Gson 방식보다 다소 복잡하고 내 Car/Boat 시나리오는 처리해야 할 데이터 구조의 단순한 끝에 있습니다.

Moshi와 함께 이것을 처리하는 다른 방법이 있습니까?

답변

1

내가 고려하지 않은 한 가지는 Map<String, Object>과 같은 제네릭 형식을 사용하여 형식 어댑터를 만들 수 있다는 것입니다. 이 경우 __typename을 조회하는 VehicleAdapter을 작성할 수 있습니다.CarBoat 인스턴스를 완전히 채우거나 (선택 사항으로 Map<String, Object>을 입력으로 사용하는 CarBoat의 생성자에 위임 할 수 있음). 따라서 Gson의 접근 방식만큼이나 편리하지는 않습니다. 또한 아무 것도하지 않고 @ToJson 메서드를 사용해야합니다. 그렇지 않으면 Moshi가 형식 어댑터를 거부합니다. 하지만이 JUnit4 테스트 클래스에서 보여 주듯이이 기능은 다음과 같이 작동합니다.

import com.squareup.moshi.FromJson; 
import com.squareup.moshi.JsonAdapter; 
import com.squareup.moshi.Moshi; 
import com.squareup.moshi.ToJson; 
import com.squareup.moshi.Types; 
import org.junit.Assert; 
import org.junit.Test; 
import java.io.IOException; 
import java.lang.reflect.Type; 
import java.util.List; 
import java.util.Map; 
import static org.junit.Assert.assertEquals; 

public class Foo { 
    static abstract class Vehicle { 
    public String id; 
    public String name; 
    } 

    static class Car extends Vehicle { 
    public Integer numDoors; 
    } 

    static class Boat extends Vehicle { 
    public String propulsion; 
    } 

    static class VehicleAdapter { 
    @FromJson 
    Vehicle fromJson(Map<String, Object> raw) { 
     String typename=raw.get("__typename").toString(); 
     Vehicle result; 

     if (typename.equals("Car")) { 
     Car car=new Car(); 

     car.numDoors=((Double)raw.get("numDoors")).intValue(); 
     result=car; 
     } 
     else if (typename.equals("Boat")) { 
     Boat boat=new Boat(); 

     boat.propulsion=raw.get("propulsion").toString(); 
     result=boat; 
     } 
     else { 
     throw new IllegalStateException("Could not identify __typename: "+typename); 
     } 

     result.id=raw.get("id").toString(); 
     result.name=raw.get("name").toString(); 

     return(result); 
    } 

    @ToJson 
    String toJson(Vehicle vehicle) { 
     throw new UnsupportedOperationException("Um, why is this required?"); 
    } 
    } 

    static final String JSON="[\n"+ 
    " {\n"+ 
    " \"__typename\": \"Car\",\n"+ 
    " \"id\": \"123\",\n"+ 
    " \"name\": \"Toyota Prius\",\n"+ 
    " \"numDoors\": 4\n"+ 
    " },\n"+ 
    " {\n"+ 
    " \"__typename\": \"Boat\",\n"+ 
    " \"id\": \"4567\",\n"+ 
    " \"name\": \"U.S.S. Constitution\",\n"+ 
    " \"propulsion\": \"SAIL\"\n"+ 
    " }\n"+ 
    "]"; 

    @Test 
    public void deserializeGeneric() throws IOException { 
    Moshi moshi=new Moshi.Builder().add(new VehicleAdapter()).build(); 
    Type payloadType=Types.newParameterizedType(List.class, Vehicle.class); 
    JsonAdapter<List<Vehicle>> jsonAdapter=moshi.adapter(payloadType); 
    List<Vehicle> result=jsonAdapter.fromJson(JSON); 

    assertEquals(2, result.size()); 

    assertEquals(Car.class, result.get(0).getClass()); 

    Car car=(Car)result.get(0); 

    assertEquals("123", car.id); 
    assertEquals("Toyota Prius", car.name); 
    assertEquals((long)4, (long)car.numDoors); 

    assertEquals(Boat.class, result.get(1).getClass()); 

    Boat boat=(Boat)result.get(1); 

    assertEquals("4567", boat.id); 
    assertEquals("U.S.S. Constitution", boat.name); 
    assertEquals("SAIL", boat.propulsion); 
    } 
} 
+1

다형식 역 직렬화는 이제 Moshi의 향후 1.4 릴리스에서 훨씬 더 잘 지원됩니다. (모든 세부 사항에 대한 링크는 https://github.com/square/moshi/issues/89를 참조하십시오.) 게시물을 보내 주셔서 감사합니다. 나는 지금 Moshi 꼬리표에 따라있을 것이다, 그래서 어떤 질문든지 및 모든 질문에 쏘십시오! –

+0

@EricCochran : 정보 주셔서 감사합니다! 그러나 문제를 잘못 읽고있는 경우가 아니라면이 문제는 다형성 직렬화와 관련이없는 것처럼 보입니다. – CommonsWare

+0

아마도 가장 중요한 것은'JsonAdapter.fromJsonValue (Object)'(https://github.com/square/moshi/pull/234/files)는 Maps 등에서 이러한 값을 만드는 데 도움이됩니다. –