如何指示Jackson ObjectMapper不将数字字段值转换为字符串属性?

huangapple go评论73阅读模式
英文:

How to instruct Jackson ObjectMapper to not convert number field value into a String property?

问题

我有以下的JSON示例:

{
    "channel": "VTEX",
    "data": "{}",
    "refId": 143433.344,
    "description": "teste",
    "tags": ["tag1", "tag2"]
}

这应该映射到以下的类:

public class AddConfigInput {
    public String channel;
    public String data;
    public String refId;
    public String description;
    public String[] tags;

    public AddConfigInput() {
    }
}

使用如下的代码:

ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
String json = 包含前面提到的JSON的字符串;
AddConfigInput obj = mapper.readValue(json, AddConfigInput.class);
System.out.println(mapper.writeValueAsString(obj));

这将产生以下输出:

{"channel":"VTEX","data":"{}","refId":"143433.344","description":"teste","tags":["tag1","tag2"]}

请注意,字段 refId 的类型是 String,我希望避免将数字自动转换为字符串属性的情况。而是希望Jackson抛出有关类型不匹配的错误。如何实现这一点呢?

英文:

I have the following JSON sample:

{
    "channel": "VTEX",
    "data": "{}",
    "refId": 143433.344,
    "description": "teste",
    "tags": ["tag1", "tag2"]
}

That should map to the following class:

public class AddConfigInput {
    public String channel;
    public String data;
    public String refId;
    public String description;
    public String[] tags;

    public AddConfigInput() {
    }
}

Using a code like bellow:

ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
String json = STRING_CONTAINING_THE_PREVIOUS_INFORMED_JSON;
AddConfigInput obj = mapper.readValue(json, AddConfigInput.class);
System.out.println(mapper.writeValueAsString(obj));

That produces as output:

{"channel":"VTEX","data":"{}","refId":"143433.344","description":"teste","tags":["tag1","tag2"]}

Please note that the field refId is of type String and I want to avoid this kind of automatic conversion from Numbers to String properties. Instead I want to Jackson throws an error about the type mismatch. How can I do that?

答案1

得分: 0

Custom deserializer used for force datatype check.

KeepStringDeserializer.java

package oct2020.json;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

public class KeepStringDeserializer extends JsonDeserializer<String> {

    @Override
    public String deserialize(JsonParser jsonParser,
            DeserializationContext deserializationContext) throws IOException {
        if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
            throw deserializationContext.wrongTokenException(jsonParser,
                    String.class, JsonToken.VALUE_STRING,
                    "Expected value is string but other datatype found.");
        }
        return jsonParser.getValueAsString();
    }
}

AddConfigInput.java

For refId attribute using custom deserializer.

package oct2020.json;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

public class AddConfigInput {
    public String channel;
    public String data;
    @JsonDeserialize(using = KeepStringDeserializer.class)
    public String refId;
    public String description;
    public String[] tags;

    public AddConfigInput() {
    }
}

TestClient.java

package oct2020.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestClient {
    public static void main(String[] args) throws JsonMappingException,
            JsonProcessingException {
        String json = "{\"channel\": \"VTEX\",\"data\": \"{}\",\"refId\": 143433.344,\"description\": \"teste\",\"tags\": [\"tag1\", \"tag2\"]}";
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
        AddConfigInput obj = mapper.readValue(json, AddConfigInput.class);
        System.out.println(mapper.writeValueAsString(obj));
    }
}

Output:

Case1: data mismatch

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_NUMBER_FLOAT), Expected value is string but other datatype found.
 at [Source: (String)"{
    "channel": "VTEX",
    "data": "{}",
    "refId": 143433.344,
    "description": "teste",
    "tags": ["tag1", "tag2"]
}"; line: 4, column: 14] (through reference chain: oct2020.json.AddConfigInput["refId"])
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)

Case2: Correct data

Input:

String json = "{\"channel\": \"VTEX\",\"data\": \"{}\",\"refId\": \"143433.344\",\"description\": \"teste\",\"tags\": [\"tag1\", \"tag2\"]}";

Output:

{"channel":"VTEX","data":"{}","refId":"143433.344","description":"teste","tags":["tag1","tag2"]}
英文:

Check if it works for you.

I have added a custom deserializer for attribute refId, there I am checking the type and in case there is a data type mismatch throwing Exception.

Custom deserializer used for force datatype check.

KeepStringDeserializer.java

package oct2020.json;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

public class KeepStringDeserializer extends JsonDeserializer&lt;String&gt; {

	@Override
	public String deserialize(JsonParser jsonParser,
			DeserializationContext deserializationContext) throws IOException {
		if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
			throw deserializationContext.wrongTokenException(jsonParser,
					String.class, JsonToken.VALUE_STRING,
					&quot;Expected value is string but other datatype found.&quot;);
		}
		return jsonParser.getValueAsString();
	}
}

AddConfigInput.java

For refId attribute using custom deserializer.

package oct2020.json;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

public class AddConfigInput {
	public String channel;
	public String data;
	@JsonDeserialize(using = KeepStringDeserializer.class)
	public String refId;
	public String description;
	public String[] tags;

	public AddConfigInput() {
	}
}

TestClient.java

package oct2020.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestClient {
	public static void main(String[] args) throws JsonMappingException,
			JsonProcessingException {
		String json = &quot;{\n    \&quot;channel\&quot;: \&quot;VTEX\&quot;,\n    \&quot;data\&quot;: \&quot;{}\&quot;,\n    \&quot;refId\&quot;: 143433.344,\n    \&quot;description\&quot;: \&quot;teste\&quot;,\n    \&quot;tags\&quot;: [\&quot;tag1\&quot;, \&quot;tag2\&quot;]\n}\&quot;&quot;;
		ObjectMapper mapper = new ObjectMapper();
		mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS);
		AddConfigInput obj = mapper.readValue(json, AddConfigInput.class);
		System.out.println(mapper.writeValueAsString(obj));
	}
}

Output:

Case1: data mismatch

Exception in thread &quot;main&quot; com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_NUMBER_FLOAT), Expected value is string but other datatype found.
 at [Source: (String)&quot;{
    &quot;channel&quot;: &quot;VTEX&quot;,
    &quot;data&quot;: &quot;{}&quot;,
    &quot;refId&quot;: 143433.344,
    &quot;description&quot;: &quot;teste&quot;,
    &quot;tags&quot;: [&quot;tag1&quot;, &quot;tag2&quot;]
}&quot;&quot;; line: 4, column: 14] (through reference chain: oct2020.json.AddConfigInput[&quot;refId&quot;])
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)

Case2: Correct data

Input:

String json = &quot;{\n    \&quot;channel\&quot;: \&quot;VTEX\&quot;,\n    \&quot;data\&quot;: \&quot;{}\&quot;,\n    \&quot;refId\&quot;: \&quot;143433.344\&quot;,\n    \&quot;description\&quot;: \&quot;teste\&quot;,\n    \&quot;tags\&quot;: [\&quot;tag1\&quot;, \&quot;tag2\&quot;]\n}\&quot;&quot;;

Output:

{&quot;channel&quot;:&quot;VTEX&quot;,&quot;data&quot;:&quot;{}&quot;,&quot;refId&quot;:&quot;143433.344&quot;,&quot;description&quot;:&quot;teste&quot;,&quot;tags&quot;:[&quot;tag1&quot;,&quot;tag2&quot;]}

答案2

得分: 0

看起来 mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS); 适用于反向情况,即在将 String 值反序列化为数值字段时会导致解析失败。

refId 字段提供自定义反序列化器似乎可以解决这个问题。

public class AddConfigInput {
    public String channel;
    public String data;

    //@JsonDeserialize(using = ForceStringDeserializer.class)
    public String refId;
    public String description;
    public String[] tags;

    public AddConfigInput() {
    }
}
public class ForceStringDeserializer extends JsonDeserializer<String> {

    @Override
    public String deserialize(
            JsonParser jsonParser, DeserializationContext deserializationContext) 
            throws IOException 
    {
        if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
            deserializationContext.reportWrongTokenException(
                    String.class, JsonToken.VALUE_STRING, 
                    "Attempted to parse token %s to string",
                    jsonParser.getCurrentToken());
        }
        return jsonParser.getValueAsString();
    }
}

Update
可以在 ObjectMapper 中注册此自定义反序列化器以覆盖默认行为:

public class ForcedStringParserModule extends SimpleModule {

    private static final long serialVersionUID = 1L;

    public ForcedStringParserModule() {
        this.setDeserializerModifier(new BeanDeserializerModifier() {

            @Override
            public JsonDeserializer<?> modifyDeserializer(
                    DeserializationConfig config, BeanDescription beanDesc,
                    JsonDeserializer<?> deserializer) 
            {
                if (String.class.isAssignableFrom(beanDesc.getBeanClass())) {
                    return new ForceStringDeserializer();
                }
                return deserializer;
            }
        });
    }
}

然后可以在 ObjectMapper 中注册此模块:

mapper.registerModule(new ForcedStringParserModule ());

在稍微修改输入 JSON(使用布尔值作为必须为字符串的 data 字段)后,会抛出以下异常:

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: 
Unexpected token (VALUE_FALSE), expected VALUE_STRING: 
Attempted to parse token VALUE_FALSE to string
 at [Source: (String)"{
    "channel": "VTEX",
    "data": false,
    "refId": "143433.344",
    "description": "teste",
    "tags": ["tag1", "tag2"]
}"; line: 3, column: 13]
英文:

It seems that mapper.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS); works for the reverse case, that is, parsing fails when deserializing String value to numeric field.

Providing custom deserializer for the refId field seems to resolve this issue.

public class AddConfigInput {
	public String channel;
	public String data;

	//@JsonDeserialize(using = ForceStringDeserializer.class)
	public String refId;
	public String description;
	public String[] tags;

	public AddConfigInput() {
	}
}
public class ForceStringDeserializer extends JsonDeserializer&lt;String&gt; {

	@Override
	public String deserialize(
            JsonParser jsonParser, DeserializationContext deserializationContext) 
            throws IOException 
    {
	    if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
	        deserializationContext.reportWrongTokenException(
                    String.class, JsonToken.VALUE_STRING, 
                    &quot;Attempted to parse token %s to string&quot;,
                    jsonParser.getCurrentToken());
	    }
	    return jsonParser.getValueAsString();
	}
}

Update<br/>
This custom deserializer may be registered within the ObjectMapper and override default behaviour:

public class ForcedStringParserModule extends SimpleModule {

	private static final long serialVersionUID = 1L;

	public ForcedStringParserModule() {
		this.setDeserializerModifier(new BeanDeserializerModifier() {

		    @Override
		    public JsonDeserializer&lt;?&gt; modifyDeserializer(
                    DeserializationConfig config, BeanDescription beanDesc,
                    JsonDeserializer&lt;?&gt; deserializer) 
            {
		        if (String.class.isAssignableFrom(beanDesc.getBeanClass())) {
		            return new ForceStringDeserializer();
		        }
		        return deserializer;
		    }
		});
	}
}

Then this module can be registered with ObjectMapper:

mapper.registerModule(new ForcedStringParserModule ());

After modifying slightly the input JSON (using boolean for data field which must be String), the following exception is thrown:

Exception in thread &quot;main&quot; com.fasterxml.jackson.databind.exc.MismatchedInputException: 
Unexpected token (VALUE_FALSE), expected VALUE_STRING: 
Attempted to parse token VALUE_FALSE to string
 at [Source: (String)&quot;{
    &quot;channel&quot;: &quot;VTEX&quot;,
    &quot;data&quot;: false,
    &quot;refId&quot;: &quot;143433.344&quot;,
    &quot;description&quot;: &quot;teste&quot;,
    &quot;tags&quot;: [&quot;tag1&quot;, &quot;tag2&quot;]
}&quot;; line: 3, column: 13]

huangapple
  • 本文由 发表于 2020年10月4日 01:26:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/64187002.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定