So verhindern Sie, dass Gson Ganzzahlen als Floats ausdrückt

71

Gson hat ein merkwürdiges Verhalten, wenn ich versuche, einen String in json zu konvertieren. Der folgende Code wandelt den Zeichenfolgenentwurf in JSON-Antworten um. Gibt es eine Möglichkeit zu verhindern, dass gson allen ganzzahligen Werten die '.0 hinzufügt?

ArrayList<Hashtable<String, Object>> responses;
Type ResponseList = new TypeToken<ArrayList<Hashtable<String, Object>>>() {}.getType();
responses = new Gson().fromJson(draft, ResponseList);

draft:
[ {"id":4077395,"field_id":242566,"body":""},
  {"id":4077398,"field_id":242569,"body":[[273019,0],[273020,1],[273021,0]]},
  {"id":4077399,"field_id":242570,"body":[[273022,0],[273023,1],[273024,0]]}
]

responses:
[ {id=4077395.0, body=, field_id=242566.0},
  {id=4077398.0, body=[[273019.0, 0.0], [273020.0, 1.0], [273021.0, 0.0]], field_id=242569.0},
  {id=4077399.0, body=[[273022.0, 0.0], [273023.0, 1.0], [273024.0, 0.0]], field_id=242570.0}
]
Mark Murphy
quelle
ähnliche Frage von double zu int: stackoverflow.com/questions/12067206/…
Patrick Kafka
1
@ PatrickKafka, aber die Antwort hier ist Gold.
Warpzit
1
Oder versuchen Sie, zu Jackson
Kirby
Siehe auch diese Antwort, die ich für eine ähnliche Frage geschrieben habe; Der Haken ist, dass Sie die Daten als analysieren Objectund dann auf das übertragen müssen, was Sie benötigen.
Aditsu beendet, weil SE am

Antworten:

44

Sie sagen Gson, dass es nach einer Liste von Karten von Strings zu Objekten sucht, die im Wesentlichen besagt, dass es eine bestmögliche Vermutung über den Typ des Objekts gibt. Da JSON nicht zwischen Ganzzahl- und Gleitkommafeldern unterscheidet, muss Gson für numerische Felder standardmäßig Gleitkomma / Double verwenden.

Gson wurde im Wesentlichen entwickelt, um den Typ des Objekts zu untersuchen, das Sie füllen möchten, um festzustellen, wie die Daten analysiert werden. Wenn Sie keinen Hinweis geben, wird es nicht sehr gut funktionieren. Eine Möglichkeit besteht darin, einen benutzerdefinierten JsonDeserializer zu definieren. Besser wäre es jedoch, keine HashMap zu verwenden (und definitiv keine Hashtable zu verwenden!) Und stattdessen Gson mehr Informationen über den erwarteten Datentyp zu geben.

class Response {
  int id;
  int field_id;
  ArrayList<ArrayList<Integer>> body; // or whatever type is most apropriate
}

responses = new Gson()
            .fromJson(draft, new TypeToken<ArrayList<Response>>(){}.getType());

Auch hier geht es bei Gson darum, strukturierte Daten nahtlos in strukturierte Objekte umzuwandeln. Wenn Sie darum bitten, eine nahezu undefinierte Struktur wie eine Liste von Objektkarten zu erstellen, besiegen Sie den gesamten Punkt von Gson und können auch einen einfacheren JSON-Parser verwenden.

dimo414
quelle
Der Vollständigkeit halber ist ein Float nicht allgemeiner als eine Ganzzahl, da es nicht alle Werte einer Ganzzahl genau wiedergeben kann. Aber ein Double kann.
Brianmearns
Ich meinte generisch in Bezug auf Typen - Gleitkomma kann nicht ganzzahlige Werte darstellen. In der Praxis kann float / double zwar nicht mehr Werte als int / long darstellen, aber die Einschränkungen des Gleitkommas sind hier nicht wirklich umstritten.
dimo414
schrecklicher Rat, sorry, aber wenn sich der Wert von int zu string zu etwas anderem als double ändert, sind Sie
fertig
@Enerccio was meinst du mit " wenn sich der Wert von int zu string zu etwas anderem als double ändert "? Wann würde sich der Werttyp ändern? Wenn sich das Schema Ihres Dokuments ändert, müssen Sie Ihre Java-Klassendefinition aktualisieren.
dimo414
@ dimo414 Nun, vielleicht kann es verschiedene Werte verschiedener Typen speichern, trotzdem habe ich dies sortiert, indem ich TaggedValueTyp mit dem Wert
Enerccio
31

Das funktioniert:

 Gson gson = new GsonBuilder().
        registerTypeAdapter(Double.class,  new JsonSerializer<Double>() {   

    @Override
    public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
        if(src == src.longValue())
            return new JsonPrimitive(src.longValue());          
        return new JsonPrimitive(src);
    }
 }).create();
Martin Wickman
quelle
11
Hallo, ich habe diese Antwort gefunden und die Art und Weise verwendet, die Sie in diesem Beitrag erwähnt haben, aber trotzdem habe ich das Doppelte bekommen, wenn es int sein sollte :-(
armnotstrong
@armnotstrong Für welche Nummer hat das nicht funktioniert? Der obige Code sollte für alle 32-Bit-Int-Werte funktionieren, da alle entsprechende exakte Werte für den Java-Doppeltyp (mit 64 Bit) haben. Casts zwischen (integralen) Doppel- und Int-Werten und zurück liegen genau im Int-Bereich. Im 64-Bit-Bereich können jedoch positive oder negative Werte über 2 hoch 52 (4.503.599.627.370.496) nicht mehr in allen Fällen korrekt konvertiert werden.
Alexander233
11

Grundsätzlich gibt es keine perfekte Antwort auf dieses Problem. Alle "Lösungen" funktionieren nur in einigen Fällen. Dies ist ein Problem, das dem gson-Team gemeldet wurde. Leider scheinen sie darauf zu bestehen, dass "Javascript keinen ganzzahligen Typ hat", als ob sie nicht erkennen, dass gson für Java und nicht für Javascript ist. Deshalb haben sie sich bis heute (jetzt 2018) geweigert, das Problem zu beheben, obwohl andere Bibliotheken wie Jackson überhaupt kein solches Problem haben, obwohl es so einfach zu beheben ist. Daher müssen Sie das Problem möglicherweise selbst aus dem gson-Quellcode beheben und Ihre eigene gson.jar erstellen. Die Quelldatei istgson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java

case NUMBER:
   return in.nextDouble();
Leon
quelle
7

Ich bin zu spät zur Party, aber ich bin selbst darauf gestoßen. In meinem Fall wollte ich in meiner ArrayList keinen Integer-Typ angeben, da es sich um einen String oder eine Integer handeln kann.

Meine Lösung lautet wie folgt:

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Double.class,  new JsonSerializer<Double>() {

    public JsonElement serialize(Double src, Type typeOfSrc,
                JsonSerializationContext context) {
            Integer value = (int)Math.round(src);
            return new JsonPrimitive(value);
        }
    });

Gson gs = gsonBuilder.create();

Anstatt die Standard-Gson-Definition mit zu verwenden Gson gs = new Gson();, habe ich die Double.class-Serialisierung überschrieben, um eine Ganzzahl zurückzugeben.

In meinem Fall habe ich Strings und Integers in meinem JSON, aber ich habe keine Doubles, so dass dies kein Problem darstellt.

Wenn Sie einen doppelten oder einen Gleitkommawert benötigen, ist es wahrscheinlich möglich, eine Logik hinzuzufügen, die den Wert auf Attribute testet, die für jeden Datentyp spezifisch sind, und einen geeigneten Wert zurückgibt. Etwas wie

if(/*source has a decimal point*/){
  return new JsonPrimitive(src); 
} else if (/* source has some attribute of a Float */){
  Float value = /*convert the src value from double to a Float */;
  return new JsonPrimitive(value);
} else {
  //it looks like an integer
  Integer value = (int)Math.round(src);
  return new JsonPrimitive(value);
}

Ich weiß nicht, wie ich diese Datentypen testen oder konvertieren soll, aber das sollte Sie auf den richtigen Weg bringen.

Steve Kallestad
quelle
3
Zu hören, " es könnte ein String oder eine Ganzzahl sein ", ist für mich eine ziemlich große rote Fahne. Es hört sich so an, als ob Ihre JSON-Daten nicht gut strukturiert sind. Sie erstellen eine Liste mit Ganzzahlen und Zeichenfolgen. Technisch gesehen erlaubt die JSON-Spezifikation dies, aber es wird jedem Deserializer, der versucht, eine Schnittstelle zu ihm herzustellen, Schmerzen bereiten. Betrachten Sie stattdessen a) das Verlassen der gesamten Liste Strings, wenn es nur ein Zufall ist, dass es sich bei einigen um Zahlen handelt, b) das Aufteilen der Zahlen in eine eigene Liste oder c) das Ändern des Listentyps in ein komplexeres Objekt, das die Absicht von besser widerspiegelt die Daten.
dimo414
@ dimo414 Nun, alle drei Lösungen haben ihre Fehler: a) ist nutzlos, da Sie dann nicht wissen, welche Nummer und welche Zeichenfolge war; b) das die Bestellinformationen verliert (die dann eine spearate Liste von Indizes und Overhead benötigen); c) Komplexe Objekte werden das Ergebnis json
Enerccio
@Enerccio komplexe Daten erfordern komplexe Darstellungen, daher ist c) oft ein akzeptabler Kompromiss. Ihre Punkte sind gut aufgenommen, aber in der Praxis werde ich mich damit zufrieden geben, dass das Mischen von Datentypen im Allgemeinen schwieriger sein wird als es sich lohnt. Wenn Sie Ihre Anforderungen normalerweise erneut prüfen, wird eine alternative Struktur angezeigt, die für Ihre Zwecke funktioniert, ohne springen zu müssen durch solche Reifen. Fühlen Sie sich frei, eine Frage mit einem konkreten Anwendungsfall zu posten, ich würde gerne abwägen.
dimo414
1

Diese Arbeit für mich.

Schritt 1: Kopieren Sie den ObjectTypeAdapter in gson in das Projekt, und behalten Sie dabei den Pfad wie in gson bei

com
  - xxx
    - xxx
com
  - google
    - gson
      - internal
        - bind
          ObjectTypeAdapter

Schritt 2: Ändern Sie ObjectTypeAdapter

case NUMBER:
  return in.nextDouble();

Geändert zu

case NUMBER:
  String number = in.nextString();
  try {
    return Long.valueOf(number);
  } catch (NumberFormatException e) {
    return Double.valueOf(number);
  }

IN ORDNUNG. Gson priorisiert den ObjectTypeAdapter im Projekt.

XiangYun
quelle
Das ist keine Lösung, sondern eine gefährliche Problemumgehung, die das gesamte Projekt zu einem Mülleimer macht
Farid,
0
    fun jsonToMap(json: JSONObject): Map<String, Any> {
        val doubles = Gson().fromJson<Map<String, Any>>(json.toString(), Map::class.java)
        fun doublesToLong(doubles: Map<String, Any>): Map<String, Any> = doubles
                .map { entry ->
                    Pair(entry.key, entry.value.let {
                        when (it) {
                            is Map<*, *> -> doublesToLong(it as Map<String, Any>)
                            is Double -> it.toLong()
                            else -> it
                        }
                    })
                }
                .toMap()
        return doublesToLong(doubles)
    }
Kreker
quelle