Der Gson-int-Typ wird in eine doppelte Problemlösung konvertiert (perfekte Lösung).

1. Wiederauftreten des Problems

1.1. BaseResponse-Klasse

class BaseResponse<T>(
    val code: Int = -1,
    val message: String? = null
) {
    var data: T? = null

    fun isSuccess(): Boolean {
        return code == 0
    }
}

1.2. Verwenden Sie Gson.fromJson ohne jegliche Verarbeitung direkt zum Parsen

val json = "{\"code\":200,\"message\":\"成功\",\"data\":{\"ints\":200,\"doubles\":200.98,\"floats\":29.0986,\"longs\":29323627832875342,\"string\":\"字符串\"}}"
val baseResponse = Gson().fromJson(json, BaseResponse::class.java)
println(JsonUtils.toJson(baseResponse))

1.3. Analyseergebnisse

I/System.out: {"code":200,"data":{"ints":200.0,"doubles":200.98,"floats":29.0986,"longs":2.9323627832875344E16,"string":"字符串"},"message":"成功"}

1.4. Problem

„ints“ vom Typ Int werden in den Typ double konvertiert, und „longs“ vom Typ Long werden ebenfalls konvertiert.

2. Lösung

2.1. Alt (die gleichen Kopierlösungen im Internet nützen eigentlich nichts )

Im Internet ist es üblich, eine neue Unterklasse der TypeAdapter-Schnittstelle zu erstellen und diese manuell zu konvertieren. MyDataTypeAdapter 

package com.xxx.baseapp.net;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.ToNumberPolicy;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.internal.bind.ObjectTypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;


public final class MyDataTypeAdapter extends TypeAdapter<Object> {
    public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
        @SuppressWarnings("unchecked")
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (type.getRawType() == Object.class) {
                return (TypeAdapter<T>) new MapTypeAdapter(gson);
            }
            return null;
        }
    };

    private final Gson gson;

    private MyDataTypeAdapter(Gson gson) {
        this.gson = gson;
    }

    @Override
    public Object read(JsonReader in) throws IOException {
        JsonToken token = in.peek();
        //判断字符串的实际类型
        switch (token) {
            case BEGIN_ARRAY:
                List<Object> list = new ArrayList<>();
                in.beginArray();
                while (in.hasNext()) {
                    list.add(read(in));
                }
                in.endArray();
                return list;

            case BEGIN_OBJECT:
                Map<String, Object> map = new LinkedTreeMap<>();
                in.beginObject();
                while (in.hasNext()) {
                    map.put(in.nextName(), read(in));
                }
                in.endObject();
                return map;
            case STRING:
                return in.nextString();
            case NUMBER:
                String s = in.nextString();
                if (s.contains(".")) {
                    return Double.valueOf(s);
                } else {
                    try {
                        return Integer.valueOf(s);
                    } catch (Exception e) {
                        return Long.valueOf(s);
                    }
                }
            case BOOLEAN:
                return in.nextBoolean();
            case NULL:
                in.nextNull();
                return null;
            default:
                throw new IllegalStateException();
        }
    }

    @Override
    public void write(JsonWriter out, Object value) throws IOException {
        if (value == null) {
            out.nullValue();
            return;
        }
        //noinspection unchecked
        TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
        if (typeAdapter instanceof ObjectTypeAdapter) {
            out.beginObject();
            out.endObject();
            return;
        }
        typeAdapter.write(out, value);
    }

    /**
     * 使用自定义工厂方法取代 Gson 实例中的工厂方法
     */
    public Gson getGson() {
        Gson gson = new GsonBuilder().create();
        try {
            Field factories = Gson.class.getDeclaredField("factories");
            factories.setAccessible(true);
            Object o = factories.get(gson);
            Class<?>[] declaredClasses = Collections.class.getDeclaredClasses();
            for (Class c : declaredClasses) {
                if ("java.util.Collections$UnmodifiableList".equals(c.getName())) {
                    Field listField = c.getDeclaredField("list");
                    listField.setAccessible(true);
                    List<TypeAdapterFactory> list = (List<TypeAdapterFactory>) listField.get(o);
                    int i = list.indexOf(ObjectTypeAdapter.getFactory(ToNumberPolicy.DOUBLE));
                    list.set(i, MyDataTypeAdapter.FACTORY);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return gson;
    }

} 

2.2. Ändern Sie den Plan (fast gelöst, nachdem Sie den Quellcode gelesen haben)

2.2.1. Analyse

Durch Analyse des Quellcodes der Konstruktionsmethode von GsonBuilder:

   /**
   * Creates a GsonBuilder instance that can be used to build Gson with various configuration
   * settings. GsonBuilder follows the builder pattern, and it is typically used by first
   * invoking various configuration methods to set desired options, and finally calling
   * {@link #create()}.
   */
  public GsonBuilder() {
  }

  /**
   * Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder
   * has the same configuration as the previously built Gson instance.
   *
   * @param gson the gson instance whose configuration should by applied to a new GsonBuilder.
   */
  GsonBuilder(Gson gson) {
    ......
    this.objectToNumberStrategy = gson.objectToNumberStrategy;
    this.numberToNumberStrategy = gson.numberToNumberStrategy;
    this.reflectionFilters.addAll(gson.reflectionFilters);
  }


  /**
   * Configures Gson to apply a specific number strategy during deserialization of {@link Object}.
   *
   * @param objectToNumberStrategy the actual object-to-number strategy
   * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
   * @see ToNumberPolicy#DOUBLE The default object-to-number strategy
   * @since 2.8.9
   */
  public GsonBuilder setObjectToNumberStrategy(ToNumberStrategy objectToNumberStrategy) {
    this.objectToNumberStrategy = Objects.requireNonNull(objectToNumberStrategy);
    return this;
  }

Es kann festgestellt werden, dass die Verarbeitungsstrategie zum Konvertieren von Objekt- und Zahlenklassen in numerische Typen während der Konstruktion standardmäßig festgelegt wird. Es ist ersichtlich, dass die Objektkonvertierung standardmäßig auf den Dobule-Typ ToNumberPolicy.DOUBLE erfolgt, sodass nicht nur int in double und Long konvertiert wird Typen. Numerische Typen werden standardmäßig in Dobule-Typen konvertiert.

2.2.2. Strategiekategorie

/**
 * An enumeration that defines two standard number reading strategies and a couple of
 * strategies to overcome some historical Gson limitations while deserializing numbers as
 * {@link Object} and {@link Number}.
 *
 * @see ToNumberStrategy
 * @since 2.8.9
 */
public enum ToNumberPolicy implements ToNumberStrategy {

  /**
   * Using this policy will ensure that numbers will be read as {@link Double} values.
   * This is the default strategy used during deserialization of numbers as {@link Object}.
   */
  DOUBLE {
    @Override public Double readNumber(JsonReader in) throws IOException {
      return in.nextDouble();
    }
  },

  /**
   * Using this policy will ensure that numbers will be read as a lazily parsed number backed
   * by a string. This is the default strategy used during deserialization of numbers as
   * {@link Number}.
   */
  LAZILY_PARSED_NUMBER {
    @Override public Number readNumber(JsonReader in) throws IOException {
      return new LazilyParsedNumber(in.nextString());
    }
  },

  /**
   * Using this policy will ensure that numbers will be read as {@link Long} or {@link Double}
   * values depending on how JSON numbers are represented: {@code Long} if the JSON number can
   * be parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a
   * {@code Double} value. If the parsed double-precision number results in a positive or negative
   * infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the
   * {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException}
   * is thrown.
   */
  LONG_OR_DOUBLE {
    @Override public Number readNumber(JsonReader in) throws IOException, JsonParseException {
      String value = in.nextString();
      try {
        return Long.parseLong(value);
      } catch (NumberFormatException longE) {
        try {
          Double d = Double.valueOf(value);
          if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
            throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
          }
          return d;
        } catch (NumberFormatException doubleE) {
          throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE);
        }
      }
    }
  },

  /**
   * Using this policy will ensure that numbers will be read as numbers of arbitrary length
   * using {@link BigDecimal}.
   */
  BIG_DECIMAL {
    @Override public BigDecimal readNumber(JsonReader in) throws IOException {
      String value = in.nextString();
      try {
        return new BigDecimal(value);
      } catch (NumberFormatException e) {
        throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
      }
    }
  }

}

Durch den obigen Quellcode der Richtlinienklasse haben wir Folgendes festgestellt: LAZILY_PARSED_NUMBER  ist eine Aufzählung, die numerische Typen verarbeitet. Beim Betrachten des LazilyParsedNumber-Quellcodes haben wir festgestellt, dass die detaillierte Verarbeitung numerischer Typen tatsächlich unterteilt ist:

/**
 * This class holds a number value that is lazily converted to a specific number type
 *
 * @author Inderjeet Singh
 */
@SuppressWarnings("serial") // ignore warning about missing serialVersionUID
public final class LazilyParsedNumber extends Number {
  private final String value;

  /** @param value must not be null */
  public LazilyParsedNumber(String value) {
    this.value = value;
  }

  @Override
  public int intValue() {
    try {
      return Integer.parseInt(value);
    } catch (NumberFormatException e) {
      try {
        return (int) Long.parseLong(value);
      } catch (NumberFormatException nfe) {
        return new BigDecimal(value).intValue();
      }
    }
  }

  @Override
  public long longValue() {
    try {
      return Long.parseLong(value);
    } catch (NumberFormatException e) {
      return new BigDecimal(value).longValue();
    }
  }

  @Override
  public float floatValue() {
    return Float.parseFloat(value);
  }

  @Override
  public double doubleValue() {
    return Double.parseDouble(value);
  }

  @Override
  public String toString() {
    return value;
  }

  /**
   * If somebody is unlucky enough to have to serialize one of these, serialize
   * it as a BigDecimal so that they won't need Gson on the other side to
   * deserialize it.
   */
  private Object writeReplace() throws ObjectStreamException {
    return new BigDecimal(value);
  }

  private void readObject(ObjectInputStream in) throws IOException {
    // Don't permit directly deserializing this class; writeReplace() should have written a replacement
    throw new InvalidObjectException("Deserialization is unsupported");
  }

  @Override
  public int hashCode() {
    return value.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj instanceof LazilyParsedNumber) {
      LazilyParsedNumber other = (LazilyParsedNumber) obj;
      return value == other.value || value.equals(other.value);
    }
    return false;
  }
}

 2.2.2. Endgültige Lösung

Da wir nun wissen, dass die Standardeinstellung  „ToNumberPolicy.DOUBLE“ während der Erstellung festgelegt wird , ist es einfach, sie zu ändern. Wir können die Richtlinienmethode aufrufen, um die Konvertierung festzulegen:

fun getGson(): Gson {
    val gsonBuilder = GsonBuilder()
    gsonBuilder.setObjectToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER)
    return gsonBuilder.create()
}

Rufen Sie die getGson-Analyse auf, nachdem Sie den Strategieplan geändert haben 

val json = "{\"code\":200,\"message\":\"成功\",\"data\":{\"ints\":200,\"doubles\":200.98,\"floats\":29.0986,\"longs\":29323627832875342,\"string\":\"字符串\"}}"
val baseResponse = getGson().fromJson(json, BaseResponse::class.java)
println(JsonUtils.toJson(baseResponse))

Testergebnisse:

I/System.out: {"code":200,"data":{"ints":200,"doubles":200.98,"floats":29.0986,"longs":29323627832875342,"string":"字符串"},"message":"成功"}

Das ist die perfekte Lösung! ! ! !

Das Internet ist voll von Copy-and-Paste-Beiträgen, die sagen, dass dies und jenes gelöst werden kann, aber die Ergebnisse sind inkonsistent. Dieses Problem beschäftigte mich 2 Tage lang und wurde schließlich perfekt gelöst

Ich denke du magst

Origin blog.csdn.net/TomLeisen/article/details/129848521
Empfohlen
Rangfolge