【问题标题】:GSON custom serializer for Map with Enum keys带有 Enum 键的 Map 的 GSON 自定义序列化程序
【发布时间】:2012-03-23 18:52:49
【问题描述】:

我正在序列化的对象包含一个映射,它的键是枚举。这些枚举有一个变量。当我使用 GSON 对其进行序列化时,我希望生成的 JSON 具有 Enum 变量而不是默认的 Enum 名称。我试过创建一个自定义序列化程序并注册它,但它没有起到作用。这是我的代码。

控制器:

@Controller
public class CheckoutClientController {

@Autowired
private Gson gson;
@Autowired
private RequestHelper requestHelper;
@Autowired
private SettingsReader settingsReader;

@InitBinder
public void initBinder(final WebDataBinder binder) {
    binder.registerCustomEditor(CheckoutConfigurationDto.class, new JsonDeserializerPropertyEditor<CheckoutConfigurationDto>(gson, CheckoutConfigurationDto.class));
}

/**
 * Handles requests to the Checkout Client page, which is the outer wrapper that includes the white label checkout (WLC) iframe. Sets up the configuration
 * data needed to pass to the WLC server.
 * 
 * @return the model and view
 */
@RequestMapping(value = "/checkout/checkout-client.ep", method = RequestMethod.GET)
public ModelAndView showPage(HttpServletRequest request) {
    CheckoutClientConfigurationDto checkoutClientConfig = new CheckoutClientConfigurationDto();

    StringBuilder host = new StringBuilder();
    host.append(request.getScheme()).append("://");
    host.append(request.getServerName());
    host.append(":").append(request.getServerPort());

    checkoutClientConfig.setWlcHost(host.toString());
    checkoutClientConfig.setClientId("clientId");
    checkoutClientConfig.setAppId("appId");
    checkoutClientConfig.setId("wlc-widget");

    Map<CheckoutClientConfigurationOption, Boolean> options = checkoutClientConfig.getOptions();

    options.put(CheckoutClientConfigurationOption.SHOW_ORDER_CONFIRMATION,
            Boolean.valueOf(this.settingsReader.getSettingValue(SettingsConstants.SHOW_ORDER_CONFIRMATION).getValue()));
    options.put(CheckoutClientConfigurationOption.REMOVE_CART_ITEMS,
            Boolean.valueOf(this.settingsReader.getSettingValue(SettingsConstants.REMOVE_CART_ITEMS).getValue()));

    return new ModelAndView(ViewConstants.CHECKOUT_CLIENT_TEMPLATE_PATH, "checkoutClientConfig", gson.toJson(checkoutClientConfig));
}
}

CheckoutClientConfigurationDto(减去所有样板的 getter/setter):

public class CheckoutClientConfigurationDto implements Dto {

private String wlcHost;

private String clientId;

private String appId;

private String id;

private Map<CheckoutClientConfigurationOption, Boolean> options;

public CheckoutClientConfigurationDto() {
    products = new ArrayList<ProductDto>();
    options = new HashMap<CheckoutClientConfigurationOption, Boolean>();
}

public Map<CheckoutClientConfigurationOption, Boolean> getOptions() {
    return options;
}

public void setOptions(final Map<CheckoutClientConfigurationOption, Boolean> options) {
    this.options = options;
}
}

CheckoutClientConfigurationOption:

public enum CheckoutClientConfigurationOption {

SHOW_SAVED_ADDRESSES("showSavedAddresses", true),
SHOW_CART_SUMMARY("showCartSummary", true),
REMOVE_CART_ITEMS("removeCartItems", true),
SHOW_DISCOUNT_FIELD("showDiscountField", true),
SHOW_VAT_CODE("showVatCode", true),
SHOW_ORDER_CONFIRMATION("showOrderConfirmation", true),
SHOW_CANCEL_BUTTON("showCancelButton", false),
SINGLE_PAGE_CHECKOUT("singlePageCheckout", false),
SEND_ORDER_CONFIRMATION_EMAIL("sendOrderConfirmationEmail", true),
SEND_SHIPPING_CONFIRMATION_EMAIL("sendShippingConfirmationEmail", true);

private String optionName;

private boolean defaultValue;

private CheckoutClientConfigurationOption(final String optionName, final boolean defaultValue) {
    this.optionName = optionName;
    this.defaultValue = defaultValue;
}

public boolean getDefautValue() {
    return defaultValue;
}

public String getOptionName() {
    return optionName;
}
}

我的自定义 GSON 序列化器:

public class CheckoutClientConfigurationOptionGsonSerializer implements JsonSerializer<CheckoutClientConfigurationOption> {

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

}

我的自定义 GSON 配置器:

public class GsonConfigurer {

private Map<Class<?>, Object> typeAdapterMap;

public Gson create() {
    final GsonBuilder gsonBuilder = new GsonBuilder();

    for (final Entry<Class<?>, Object> typeAdapterMapping : typeAdapterMap.entrySet()) {
        gsonBuilder.registerTypeAdapter(typeAdapterMapping.getKey(), typeAdapterMapping.getValue());
    }

    return gsonBuilder.create();
}

protected Map<Class<?>, Object> getTypeAdapterMap() {
    return typeAdapterMap;
}

public void setTypeAdapterMap(final Map<Class<?>, Object> typeAdapterMap) {
    this.typeAdapterMap = typeAdapterMap;
}

}

XML:

<bean id="gsonConfigurer" class="com.sfweb.gson.GsonConfigurer">
    <property name="typeAdapterMap">
        <util:map key-type="java.lang.Class">
            <entry key="com.sfweb.dto.CheckoutConfigurationOption">
                <bean class="com.sfweb.dto.deserializer.CheckoutConfigurationOptionGsonDeserializer" />
            </entry>
            <entry key="com.sfweb.dto.CheckoutClientConfigurationOption">
                <bean class="com.sfweb.dto.serializer.CheckoutClientConfigurationOptionGsonSerializer" />
            </entry>
        </util:map>
    </property>
</bean>

<bean class="com.google.gson.Gson" factory-bean="gsonConfigurer" factory-method="create" />

我还有一个自定义反序列化器,正如您在 XML 中看到的那样。那个工作没有问题。我在调试模式下运行,并且 CheckoutClientConfigurationOptionGsonSerializer 中的行从未被命中。我检查了我调用 toJson() 的 gson 对象中是否包含自定义序列化程序。所以我不确定问题是什么。我有一种感觉,我只是少了一件。

我希望生成的 JSON 显示“showSavedAddresses”,但它显示的是“SHOW_SAVED_ADDRESSES”。提前感谢您的帮助!

【问题讨论】:

  • 我遇到了同样的问题。我的猜测是,由于 JS 对象上的键最终需要是字符串,所以它只是运行 value.toString() 而不是首选的 value.toJsonAndRunCustomSerializer().toString()

标签: json spring serialization gson


【解决方案1】:

阅读GsonBuilder#enableComplexMapKeySerialization 上的文档我看到了:

map序列化的默认实现在key上使用toString()

所以默认情况下它不会在地图键上运行您的TypeAdapter。我尝试简单地调用此方法并让我的枚举显示为数字字符串。

【讨论】:

    【解决方案2】:

    你会很高兴学习TypeAdapterFactory's documentation。它包括一个将枚举格式化为小写的示例;您可以根据自己的需要进行修改。

    public class LowercaseEnumTypeAdapterFactory implements TypeAdapter.Factory {
      public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        Class<T> rawType = (Class<T>) type.getRawType();
        if (!rawType.isEnum()) {
          return null;
        }
    
        final Map<String, T> lowercaseToConstant = new HashMap<String, T>();
        for (T constant : rawType.getEnumConstants()) {
          lowercaseToConstant.put(toLowercase(constant), constant);
        }
    
        return new TypeAdapter<T>() {
          public void write(JsonWriter out, T value) throws IOException {
            if (value == null) {
              out.nullValue();
            } else {
              out.value(toLowercase(value));
            }
          }
    
          public T read(JsonReader reader) throws IOException {
            if (reader.peek() == JsonToken.NULL) {
              reader.nextNull();
              return null;
            } else {
              return lowercaseToConstant.get(reader.nextString());
            }
          }
        };
      }
    
      private String toLowercase(Object o) {
        return o.toString().toLowerCase(Locale.US);
      }
    }
    

    【讨论】:

    • 我试过这条路线,我几乎得到了相同的结果。我发现问题在于我的枚举被嵌入为我的地图的键。如果我将枚举作为 DTO 中的单个变量,它将枚举序列化为小写,没有问题,但是当我将它用作映射的键​​时,它不会调用我的自定义序列化程序或我的自定义类型适配器。
    猜你喜欢
    • 2014-09-20
    • 1970-01-01
    • 2017-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-04
    相关资源
    最近更新 更多