2013-06-11 8 views
13

Mam dekoder netty, który używa GSon do konwersji JSon pochodzących z klienta WWW na odpowiednie obiekty java. Wymaganie to: Klient może wysyłać niepowiązane klasy, klasa A, klasa B, klasa C itp., Ale chciałbym użyć tej samej instancji dekodera singleton w potoku, aby wykonać konwersję (ponieważ używam sprężyny do jej konfiguracji) . Problem, przed którym stoję, to konieczność wcześniejszego zapoznania się z obiektem class.Konwersja z JSon do wielu nieznanych typów obiektów Java za pomocą GSon

public Object decode() 
{ 
    gson.fromJson(jsonString, A.class); 
} 

To nie może dekodować B lub C. użytkownicy mojej bibliotece, teraz trzeba napisać oddzielne dekodery dla każdej klasy zamiast gipsie później w dół. Jedynym sposobem, jaki mogę zobaczyć, jest przekazanie nazwy String klasy "org.example.C" w łańcuchu JSon z klienta WWW, przeanalizowanie go w dekoderze, a następnie użycie klasy Class.forName. Czy jest lepszy sposób to zrobić?

+2

Jeśli jest to komponent ogólnego przeznaczenia, pomyśl o tym, w jaki sposób użytkownicy będą go konfigurować. Rozważane są także rozsądne wartości domyślne. Czy miałoby sens posiadanie wielu instancji dekodera, po jednej na klasę? – Jukka

+0

To, jak to teraz robię, jedna instancja na typ klasy. Problem polega na tym, że użytkownicy mojej biblioteki muszą teraz wykonywać wewnętrzne szczegóły, takie jak dekodery i kodery, i konfigurować je na wiosnę za każdym razem, gdy dodawana jest nowa klasa. Chciałem ich oszczędzić i zamiast tego zrobić prostą obsadę. – Abe

Odpowiedz

10

GSon MUSI znać klasę odpowiadającą ciągowi json. Jeśli nie chcesz go podawać od Json(), możesz to określić w Jsonie. Jednym ze sposobów jest zdefiniowanie interfejsu i powiązanie z nim adaptera.

odczuwalna:

class A implements MyInterface { 
    // ... 
    } 

    public Object decode() 
    { 
    Gson gson = builder.registerTypeAdapter(MyInterface.class, new MyInterfaceAdapter()); 
    MyInterface a = gson.fromJson(jsonString, MyInterface.class); 
    } 

Adapter może być jak:

public final class MYInterfaceAdapter implements JsonDeserializer<MyInterface>, JsonSerializer<MyInterface> { 
    private static final String PROP_NAME = "myClass"; 

    @Override 
    public MyInterface deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 
    try { 
     String classPath = json.getAsJsonObject().getAsJsonPrimitive(PROP_NAME).getAsString(); 
     Class<MyInterface> cls = (Class<MyInterface>) Class.forName(classPath); 

     return (MyInterface) context.deserialize(json, cls); 
    } catch (ClassNotFoundException e) { 
     e.printStackTrace(); 
    } 

    return null; 
    } 

    @Override 
    public JsonElement serialize(MyInterface src, Type typeOfSrc, JsonSerializationContext context) { 
    // note : won't work, you must delegate this 
    JsonObject jo = context.serialize(src).getAsJsonObject(); 

    String classPath = src.getClass().getName(); 
    jo.add(PROP_NAME, new JsonPrimitive(classPath)); 

    return jo; 
    } 
} 
+0

Problem polega na tym, że klasa B nie implementowałaby 'MyInterface'. Nie są to logicznie powiązane klasy. Ale prawdopodobnie muszę sprawić, aby użytkownicy biblioteki implementowali interfejs znaczników, aby ten kod działał. – Abe

+0

Więc chciałbym iść z TypeAdapterFactory. Najważniejsze jest to, że JSon musi zapewnić klasę do utworzenia instancji. GSon nie jest magiczny, nie może dla ciebie odgadnąć :) – PomPom

+0

Od 1.7 'Builder GsonBuilder' ma [registerTypeHierarchyAdapter] (https://google.github.io/gson/apidocs/com/google/gson/GsonBuilder.html# registerTypeHierarchyAdapter-java.lang.Class-java.lang.Object-) metoda, która może być bardziej odpowiednia w tym przypadku. Zobacz [dyskusję] (https://groups.google.com/forum/#!topic/google-gson/0ebOxifqwb0). – Vadzim

5

Załóżmy, że masz te 2 możliwych odpowiedzi JSON:

{ 
    "classA": {"foo": "fooValue"} 
} 
    or 
{ 
    "classB": {"bar": "barValue"} 
} 

można utworzyć struktury klasowej tak:

public class Response { 
    private A classA; 
    private B classB; 
    //more possible responses... 
    //getters and setters... 
} 

public class A { 
    private String foo; 
    //getters and setters... 
} 

public class B { 
    private String bar; 
    //getters and setters... 
} 

Następnie można zanalizować każdy z możliwych odpowiedzi JSON z:

Response response = gson.fromJson(jsonString, Response.class); 

Gson zignoruje wszystkie pola JSON, które nie odpowiadają żadnym z atrybutów w struktury klasy, dzięki czemu można dostosować jedną klasę analizować różne odpowiedzi ...

Następnie można sprawdzić, które z atrybutów classA, classB ... nie jest null i będziesz wiedzieć, które otrzymały odpowiedź ty .

+0

To jest dla biblioteki, która jest opublikowana dla użytkowników -> https://github.com/menacher/java-game-server, więc naprawdę nie wiedziałabym ich klas. Więc nie ma możliwości wypełnienia klasy Response. – Abe

+1

@Abe, więc musisz przeanalizować kilka odpowiedzi JSON i nie wiesz jak one wyglądają w ogóle? To naprawdę ciężka praca! – MikO

+0

W rzeczywistości klient może wysłać nazwę klasy, ponieważ wie, co wysyła, więc nie jest to utracona przyczyna. – Abe

1

Tworzenie klasy modelu,

public class MyModel { 

    private String errorId; 

    public String getErrorId() { 
     return errorId; 
    } 

    public void setErrorId(String errorId) { 
     this.errorId = errorId; 
    } 
} 

Tworzenie podklasa

public class SubClass extends MyModel { 
     private String subString; 

     public String getSubString() { 
      return subString; 
     } 

     public void setSubString(String subString) { 
      this.subString = subString; 
     } 
} 

wywołanie metody parseGson

parseGson(subClass); 

metoda gson parse z klasy obiektów

public void parseGson(Object object){ 
    object = gson.fromJson(response.toString(), object.getClass()); 
    SubClass subclass = (SubClass)object; 
    } 

Można ustawić zmienne globalne, które oddane do myModel

((MyModel)object).setErrorId(response.getString("errorid")); 
3

pewien, czy to jest to, co zostało z prośbą o , ale modyfikując klasę RuntimeTypeAdapterFactory stworzyłem system do podklasy na podstawie warunków w źródle Jsona. RuntimeTypeAdapterFactory.class:

/* 
* Copyright (C) 2011 Google Inc. 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
*  http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

package com.google.gson.typeadapters; 

import java.io.IOException; 
import java.util.LinkedHashMap; 
import java.util.Map; 

import com.google.gson.Gson; 
import com.google.gson.JsonElement; 
import com.google.gson.JsonObject; 
import com.google.gson.JsonParseException; 
import com.google.gson.JsonPrimitive; 
import com.google.gson.TypeAdapter; 
import com.google.gson.TypeAdapterFactory; 
import com.google.gson.internal.Streams; 
import com.google.gson.reflect.TypeToken; 
import com.google.gson.stream.JsonReader; 
import com.google.gson.stream.JsonWriter; 

/** 
* Adapts values whose runtime type may differ from their declaration type. This 
* is necessary when a field's type is not the same type that GSON should create 
* when deserializing that field. For example, consider these types: 
* <pre> {@code 
* abstract class Shape { 
*  int x; 
*  int y; 
* } 
* class Circle extends Shape { 
*  int radius; 
* } 
* class Rectangle extends Shape { 
*  int width; 
*  int height; 
* } 
* class Diamond extends Shape { 
*  int width; 
*  int height; 
* } 
* class Drawing { 
*  Shape bottomShape; 
*  Shape topShape; 
* } 
* }</pre> 
* <p>Without additional type information, the serialized JSON is ambiguous. Is 
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code 
* { 
*  "bottomShape": { 
*  "width": 10, 
*  "height": 5, 
*  "x": 0, 
*  "y": 0 
*  }, 
*  "topShape": { 
*  "radius": 2, 
*  "x": 4, 
*  "y": 1 
*  } 
* }}</pre> 
* This class addresses this problem by adding type information to the 
* serialized JSON and honoring that type information when the JSON is 
* deserialized: <pre> {@code 
* { 
*  "bottomShape": { 
*  "type": "Diamond", 
*  "width": 10, 
*  "height": 5, 
*  "x": 0, 
*  "y": 0 
*  }, 
*  "topShape": { 
*  "type": "Circle", 
*  "radius": 2, 
*  "x": 4, 
*  "y": 1 
*  } 
* }}</pre> 
* Both the type field name ({@code "type"}) and the type labels ({@code 
* "Rectangle"}) are configurable. 
* 
* <h3>Registering Types</h3> 
* Create a {@code RuntimeTypeAdapter} by passing the base type and type field 
* name to the {@link #of} factory method. If you don't supply an explicit type 
* field name, {@code "type"} will be used. <pre> {@code 
* RuntimeTypeAdapter<Shape> shapeAdapter 
*  = RuntimeTypeAdapter.of(Shape.class, "type"); 
* }</pre> 
* Next register all of your subtypes. Every subtype must be explicitly 
* registered. This protects your application from injection attacks. If you 
* don't supply an explicit type label, the type's simple name will be used. 
* <pre> {@code 
* shapeAdapter.registerSubtype(Rectangle.class, "Rectangle"); 
* shapeAdapter.registerSubtype(Circle.class, "Circle"); 
* shapeAdapter.registerSubtype(Diamond.class, "Diamond"); 
* }</pre> 
* Finally, register the type adapter in your application's GSON builder: 
* <pre> {@code 
* Gson gson = new GsonBuilder() 
*  .registerTypeAdapter(Shape.class, shapeAdapter) 
*  .create(); 
* }</pre> 
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code 
* RuntimeTypeAdapter<Shape> shapeAdapter = RuntimeTypeAdapterFactory.of(Shape.class) 
*  .registerSubtype(Rectangle.class) 
*  .registerSubtype(Circle.class) 
*  .registerSubtype(Diamond.class); 
* }</pre> 
*/ 
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory { 
    private final Class<?> baseType; 
    private final RuntimeTypeAdapterPredicate predicate; 
    private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>(); 
    private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>(); 

    private RuntimeTypeAdapterFactory(Class<?> baseType, RuntimeTypeAdapterPredicate predicate) { 
     if (predicate == null || baseType == null) { 
      throw new NullPointerException(); 
     } 
     this.baseType = baseType; 
     this.predicate = predicate; 
    } 

    /** 
    * Creates a new runtime type adapter using for {@code baseType} using {@code 
    * typeFieldName} as the type field name. Type field names are case sensitive. 
    */ 
    public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, RuntimeTypeAdapterPredicate predicate) { 
     return new RuntimeTypeAdapterFactory<T>(baseType, predicate); 
    } 

    /** 
    * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as 
    * the type field name. 
    */ 
    public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) { 
     return new RuntimeTypeAdapterFactory<T>(baseType, null); 
    } 

    /** 
    * Registers {@code type} identified by {@code label}. Labels are case 
    * sensitive. 
    * 
    * @throws IllegalArgumentException if either {@code type} or {@code label} 
    *  have already been registered on this type adapter. 
    */ 
    public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) { 
     if (type == null || label == null) { 
      throw new NullPointerException(); 
     } 
     if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { 
      throw new IllegalArgumentException("types and labels must be unique"); 
     } 
     labelToSubtype.put(label, type); 
     subtypeToLabel.put(type, label); 
     return this; 
    } 

    /** 
    * Registers {@code type} identified by its {@link Class#getSimpleName simple 
    * name}. Labels are case sensitive. 
    * 
    * @throws IllegalArgumentException if either {@code type} or its simple name 
    *  have already been registered on this type adapter. 
    */ 
    public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) { 
     return registerSubtype(type, type.getSimpleName()); 
    } 

    public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) { 
     if (type.getRawType() != baseType) { 
      return null; 
     } 

     final Map<String, TypeAdapter<?>> labelToDelegate 
       = new LinkedHashMap<String, TypeAdapter<?>>(); 
     final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate 
       = new LinkedHashMap<Class<?>, TypeAdapter<?>>(); 
     for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) { 
      TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); 
      labelToDelegate.put(entry.getKey(), delegate); 
      subtypeToDelegate.put(entry.getValue(), delegate); 
     } 

     return new TypeAdapter<R>() { 
      @Override public R read(JsonReader in) throws IOException { 
       JsonElement jsonElement = Streams.parse(in); 
       String label = predicate.process(jsonElement); 
       @SuppressWarnings("unchecked") // registration requires that subtype extends T 
         TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label); 
       if (delegate == null) { 
        throw new JsonParseException("cannot deserialize " + baseType + " subtype named " 
          + label + "; did you forget to register a subtype?"); 
       } 
       return delegate.fromJsonTree(jsonElement); 
      } 

      @Override public void write(JsonWriter out, R value) throws IOException { // Unimplemented as we don't use write. 
       /*Class<?> srcType = value.getClass(); 
       String label = subtypeToLabel.get(srcType); 
       @SuppressWarnings("unchecked") // registration requires that subtype extends T 
         TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType); 
       if (delegate == null) { 
        throw new JsonParseException("cannot serialize " + srcType.getName() 
          + "; did you forget to register a subtype?"); 
       } 
       JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); 
       if (jsonObject.has(typeFieldName)) { 
        throw new JsonParseException("cannot serialize " + srcType.getName() 
          + " because it already defines a field named " + typeFieldName); 
       } 
       JsonObject clone = new JsonObject(); 
       clone.add(typeFieldName, new JsonPrimitive(label)); 
       for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) { 
        clone.add(e.getKey(), e.getValue()); 
       }*/ 
       Streams.write(null, out); 
      } 
     }; 
    } 
} 

RuntimeTypeAdapterPredicate.class:

package com.google.gson.typeadapters; 

import com.google.gson.JsonElement; 

/** 
* Created by Johan on 2014-02-13. 
*/ 
public abstract class RuntimeTypeAdapterPredicate { 

    public abstract String process(JsonElement element); 

} 

przykład (zaczerpnięty z projektu Obecnie pracuję na):

ItemTypePredicate.class:

package org.libpoe.serial; 

import com.google.gson.JsonElement; 
import com.google.gson.JsonObject; 
import com.google.gson.typeadapters.RuntimeTypeAdapterPredicate; 

/** 
* Created by Johan on 2014-02-13. 
*/ 
public class ItemTypePredicate extends RuntimeTypeAdapterPredicate { 

    @Override 
    public String process(JsonElement element) { 
     JsonObject obj = element.getAsJsonObject(); 
     int frameType = obj.get("frameType").getAsInt(); 

     switch(frameType) { 
      case 4: return "Gem"; 
      case 5: return "Currency"; 
     } 
     if (obj.get("typeLine").getAsString().contains("Map") 
       && obj.get("descrText").getAsString() != null 
       && obj.get("descrText").getAsString().contains("Travel to this Map")) { 
      return "Map"; 
     } 

     return "Equipment"; 
    } 
} 

Zastosowanie:

RuntimeTypeAdapterFactory<Item> itemAdapter = RuntimeTypeAdapterFactory.of(Item.class, new ItemTypePredicate()) 
     .registerSubtype(Currency.class) 
     .registerSubtype(Equipment.class) 
     .registerSubtype(Gem.class) 
     .registerSubtype(Map.class); 

Gson gson = new GsonBuilder() 
     .enableComplexMapKeySerialization() 
     .registerTypeAdapterFactory(itemAdapter).create(); 

Podstawową klasą hierachy jest pozycja. Waluta, wyposażenie, klejnot i mapa to wszystko rozszerzają.

+0

To jest prawie to, czego chcę, ale moim podstawowym wymogiem było zidentyfikowanie niespokrewnionych klas, co moim zdaniem nie jest możliwe. – Abe

Powiązane problemy