【问题标题】:"Relaxed" fields names for Jackson杰克逊的“轻松”字段名称
【发布时间】:2019-04-25 10:11:20
【问题描述】:

我正在处理Jackson 配置,我想知道是否有任何选项可以反序列化不同类型的字段模式。

例如,我有一个对象:

class DeserializeIt {
    String fieldOne;
    String fieldOneAndHalf;
    String fieldTwo;
    String fieldThree;
    String fieldFour;

   //getters setters etc.
}

我有以下JSON有效载荷:

{
    "fieldOne" : "value1",
    "field_ONE-and_Half": "value15",
    "FIELD_TWO": "value2",
    "FIELD_THREE" : "value3",
    "field_four": "value4"
}

我想毫无例外地将所有这些字段名称反序列化为驼峰式。

我尝试创建我的自定义PropertyNamingStrategy,但它从另一个方向发展:它不会将分隔字段转换为驼峰式大小写,它会尝试转换对象字段并在解析的字符串中搜索它们。

而且由于我不能传递可能的字符串列表而不是一个变体(fieldOne 可以变成 field-onefield_onefield-ONE 等),所以这不起作用。

你知道我还能为这种轻松的反序列化配置什么吗?

【问题讨论】:

  • 我不确定杰克逊是否提供了开箱即用的东西,但我会尝试提供一个自定义反序列化器,在推迟到现有的或之前转换属性名称(这可能更容易但效率稍低)将 json 读取为通用树格式(一堆嵌套的 JsonNode 对象),转换映射键(删除、转换并再次放置),将树重新格式化为 JSON,最后让 Jackson 解析重新格式化的json 到 POJO 中。

标签: java json jackson deserialization json-deserialization


【解决方案1】:

我们需要扩展 com.fasterxml.jackson.databind.deser.BeanDeserializerModifiercom.fasterxml.jackson.databind.deser.BeanDeserializer 以反序列化 POJO 类。下面的解决方案取决于您正在使用的version,因为我从基类复制了一些代码,这些代码还没有准备好拦截额外的功能。如果您的POJO 类没有任何额外配置,vanillaDeserialize 方法将被调用,我们将尝试改进这一方法。

在其他情况下,您需要调试此反序列化器并在需要时更新其他地方。以下解决方案使用版本2.9.8

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        SimpleModule relaxedModule = new SimpleModule();
        relaxedModule.setDeserializerModifier(new RelaxedBeanDeserializerModifier());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(relaxedModule);

        System.out.println(mapper.readValue(jsonFile, DeserializeIt.class));
    }
}

class RelaxedBeanDeserializerModifier extends BeanDeserializerModifier {

    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        JsonDeserializer<?> base = super.modifyDeserializer(config, beanDesc, deserializer);
        if (base instanceof BeanDeserializer) {
            return new RelaxedBeanDeserializer((BeanDeserializer) base);
        }

        return base;
    }
}

class RelaxedBeanDeserializer extends BeanDeserializer {

    private Map<String, String> properties = new HashMap<>();

    public RelaxedBeanDeserializer(BeanDeserializerBase src) {
        super(src);
        _beanProperties.forEach(property -> {
            properties.put(property.getName().toLowerCase(), property.getName());
        });
    }

    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // common case first
        if (p.isExpectedStartObjectToken()) {
            if (_vanillaProcessing) {
                return vanillaDeserialize(p, ctxt, p.nextToken());
            }
            // 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
            //    what it is, including "expected behavior".
            p.nextToken();
            if (_objectIdReader != null) {
                return deserializeWithObjectId(p, ctxt);
            }
            return deserializeFromObject(p, ctxt);
        }
        return _deserializeOther(p, ctxt, p.getCurrentToken());
    }

    protected Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt, JsonToken t) throws IOException {
        final Object bean = _valueInstantiator.createUsingDefault(ctxt);
        // [databind#631]: Assign current value, to be accessible by custom serializers
        p.setCurrentValue(bean);

        if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
            String propName = p.getCurrentName();

            do {
                String relaxedName = getRelaxedName(propName);
                String mappedName = properties.get(relaxedName);
                defaultImplementation(p, ctxt, bean, mappedName);
            } while ((propName = p.nextFieldName()) != null);
        }
        return bean;
    }

    private void defaultImplementation(JsonParser p, DeserializationContext ctxt, Object bean, String propName) throws IOException {
        p.nextToken();
        SettableBeanProperty prop = _beanProperties.find(propName);

        if (prop != null) { // normal case
            try {
                prop.deserializeAndSet(p, ctxt, bean);
            } catch (Exception e) {
                wrapAndThrow(e, bean, propName, ctxt);
            }
            return;
        }
        handleUnknownVanilla(p, ctxt, bean, propName);
    }

    private String getRelaxedName(String name) {
        return name.replaceAll("[_\\-]", "").toLowerCase();
    }
}

上面的代码打印:

DeserializeIt{fieldOne='value1', fieldOneAndHalf='value15', fieldTwo='value2', fieldThree='value3', fieldFour='value4'}

另见:

【讨论】:

    【解决方案2】:

    从 Jackson 2.9 开始,您可以使用 @JsonAlias annotation。在你的例子中,它会是这样的:

    class DeserializeIt {
      @JsonAlias("fieldOne") 
      String fieldOne;
    
      @JsonAlias("field_ONE-and_Half") 
      String fieldOneAndHalf;
    
      @JsonAlias("FIELD_TWO") 
      String fieldTwo;
    
      @JsonAlias("FIELD_THREE") 
      String fieldThree;
      // and so on...
    }
    

    【讨论】:

    • 在我的意义上放松意味着一个字段可能看起来不同:如 fieldOne、field-one、field_ONE 等,所以实际上我不知道消费者发送的确切内容。而且很难计算所有领域的所有可能性。
    【解决方案3】:

    什么对我有用:我添加了一个 AOP 组件,它将传入对象的所有字段重命名为 Camel 案例。

    【讨论】:

      猜你喜欢
      • 2020-02-12
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-27
      相关资源
      最近更新 更多