如何避免以科学记数法表示的双精度数和数字的字符串到数字转换?

Posted

技术标签:

【中文标题】如何避免以科学记数法表示的双精度数和数字的字符串到数字转换?【英文标题】:How to avoid string to number conversion for doubles and numbers expressed in Scientific Notation? 【发布时间】:2019-11-06 04:43:37 【问题描述】:

我收到JSON 有效载荷,它是一组键值对。值可以是字符串或数字。我必须解析 JSON 并将键值对存储到适当的 varchar2 列中。我应该保存传入的号码与输入有效负载中显示的完全相同。 但是对于像1.1E40.00000000000003 和类似的数字,我会得到11000.03.0E-14

这是一种禁用/防止数字转换为字符串表示的方法吗?

我使用FasterXML Jackson 实现。 顺便说一句,没有可用的实际文档——我发现的所有来源都指向http://wiki.fasterxml.com/JacksonHome,它现在已经关闭。 我在这里发现了两个类似的问题 Jackson JSON converts integers into strings Disable the Number to String automatic conversion in jackson 但是遇到号码时两者都需要例外,这不是我的情况。我尝试了建议的解决方案,但未能成功修改它们以适应我的任务。

也没有答案 https://github.com/FasterXML/jackson-databind/issues/796

现在除了键值对之外,我没有输入字符串的规范。所以只是一个例子:

可能收到如下信息:

"a":"text", "b":"35", "c":"d":"another", "e":["array",35], "f":1.1E4, "g":0.00000000000003

我想要字符串对

"a" -> "text", "b" -> "35", "c" -> "\"d\":\"another\"", "e" -> "[\"array\",35]", "f" -> "1.1E4" 

最简单的转换方式是:

public void test() throws IOException 
    Map map = new ObjectMapper().readValue(
    "\"a\":\"text\", \"b\":\"35\", \"c\":\"d\":\"another\", \"e\":[\"array\",35], \"f\":1.1E4, \"g\":0.00000000000003"
        , Map.class);
    System.out.println(map);

结果:

a=text, b=35, c=d=another, e=[array, 35], f=11000.0, g=3.0E-14

更准确的方法:

public class JsonUtil2 

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    public static Map<String, String> parse(String json) throws IOException 
        ObjectNode objectNode = (ObjectNode) OBJECT_MAPPER.readTree(json);

        Map<String, String> result = new HashMap<>(objectNode.size());
        objectNode.fields().forEachRemaining(entry -> result.put(entry.getKey(), toJson(entry.getValue())));
        return result;
    

    private static String toJson(JsonNode jsonNode) 
        if (jsonNode.isNumber()) 
            if (jsonNode instanceof DoubleNode || jsonNode instanceof FloatNode) 
                DecimalFormatSymbols dfs = new DecimalFormatSymbols();
                dfs.setDecimalSeparator('.');
                dfs.setMinusSign('-');
                DecimalFormat df = new DecimalFormat("#.#", dfs);
                df.setMaximumFractionDigits(32);
                df.setMaximumIntegerDigits(32);
                return df.format(jsonNode.doubleValue());
             else 
                return jsonNode.asText();
            
         else if (jsonNode.isValueNode()) 
            return jsonNode.asText();
         else 
            try 
                return OBJECT_MAPPER.writeValueAsString(jsonNode);
             catch (JsonProcessingException e) 
                throw new RuntimeException(e);
            
        
    

导致:

a=text, b=35, c="d":"another", e=["array",35], f=11000, g=0.00000000000003

这要好得多,但在 f=11000 而不是 f=1.1E4 中仍然存在差异。

【问题讨论】:

请展示您的 JSON 字符串以及如何解析它! 您可以使用BigIntegerBigDecimal @MC Emperor,我需要将解析的键值对作为字符串存储在数据库中。我怎么能使用 BigDecimal 呢? 【参考方案1】:

在您的情况下,您希望将所有内容都视为String,因此您需要一个自定义解串器,将JSON ObjectJSON Array 读取为String。我们还可以通过使用TypeFactory 提供此信息来强制Jackson 读取Map&lt;String, String&gt;

假设我们的JSON 有效载荷如下所示:


  "a": "text",
  "b": "35",
  "c": 
    "d": "another",
    "dd":3.44E3
  ,
  "e": [
    "array",
    35,
    2.3E5
  ],
  "f": 1.1E4,
  "g": 0.00000000000003

示例代码:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class JsonTreeApp 

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

        SimpleModule everythingIsStringModule = new SimpleModule();
        everythingIsStringModule.addDeserializer(String.class, new EverythingIsStringDeserializer());

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

        MapType mapType = mapper.getTypeFactory().constructMapType(LinkedHashMap.class, String.class, String.class);

        LinkedHashMap<String, String> map = mapper.readValue(jsonFile, mapType);
        map.forEach((k, v) -> System.out.println(k + " => " + v));
    


class EverythingIsStringDeserializer extends StringDeserializer 

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException 
        if (p.currentToken() == JsonToken.START_OBJECT) 
            return _deserializeFromObject(p, ctxt);
        
        return super.deserialize(p, ctxt);
    

    private String _deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException 
        MapType mapType = ctxt.getTypeFactory().constructMapType(LinkedHashMap.class, String.class, String.class);
        JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(mapType);
        Map<String, String> map = (Map<String, String>) deserializer.deserialize(p, ctxt);

        return toString(map);
    

    @Override
    protected String _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException 
        CollectionType collectionType = ctxt.getTypeFactory().constructCollectionType(ArrayList.class, String.class);
        JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(collectionType);
        List<String> list = (List<String>) deserializer.deserialize(p, ctxt);

        return toString(list);
    

    private String toString(Map<String, String> map) 
        StringBuilder builder = new StringBuilder(128);

        builder.append('');
        boolean addComa = false;
        for (Map.Entry<String, String> entry : map.entrySet()) 
            if (addComa) 
                builder.append(',');
            
            builder.append('"').append(entry.getKey())
                    .append("\":");
            appendValue(entry.getValue(), builder);
            addComa = true;
        
        builder.append('');

        return builder.toString();
    

    private String toString(List<String> list) 
        StringBuilder builder = new StringBuilder(128);

        builder.append('[');
        boolean addComa = false;
        for (String item : list) 
            if (addComa) 
                builder.append(',');
            
            appendValue(item, builder);
            addComa = true;
        
        builder.append(']');

        return builder.toString();
    

    private void appendValue(String value, StringBuilder builder) 
        if (value == null || value.isEmpty()) 
            builder.append("\"\"");
            return;
        
        if (Character.isAlphabetic(value.charAt(0))) 
            builder.append('"').append(value).append('"');
         else 
            builder.append(value);
        
    

打印:

a => text
b => 35
c => d=another, dd=3.44E3
e => [array, 35, 2.3E5]
f => 1.1E4
g => 0.00000000000003

【讨论】:

感谢您的回答。这要好得多。但是使用这种方式我仍然得到c =&gt; d=another, dd=3.44E3而不是c =&gt; "d:"another", "dd":3.44E3这是一种以这种方式改进的方法吗? @Roman,您必须实现自己的 JSON Object 反序列化。在_deserializeFromObject 方法中,您可以手动读取所有令牌并使用StringBuilder 构建它。或者,在阅读 Map 之后,您可以遍历所有 key-value 对并将它们包装到引号中。 @Roman,你解决了为对象包装键值对的问题吗? @Michael Zlobin,再次感谢您,您能否提供一个示例如何以这种方式实现 AbstractDeserializer?

以上是关于如何避免以科学记数法表示的双精度数和数字的字符串到数字转换?的主要内容,如果未能解决你的问题,请参考以下文章

如何使java中double类型不以科学计数法表示

打印没有科学格式的双精度并四舍五入到最接近的整数[关闭]

如何使java中double类型不以科学计数法表示

<C#>如何计算天文数字

JDBC:jtds getString() 以科学计数法返回双精度数

如何将 UI 控件的双数据值格式化为数字,例如 1234 到 1.23K? [复制]