【问题标题】:Flatten a hashmap derived from a json string in Java展平从 Java 中的 json 字符串派生的哈希图
【发布时间】:2019-06-25 20:22:05
【问题描述】:

由于各种原因,我最终遇到了需要在 android studio 中使用 java 反序列化来自 Fable F# 的 json 对象的情况。 字符串如下:

{"MainForm":{"OriginMerd":{"id":"onMggagerd","number":5,"tests":{"Blod":{"blodid":"1","glucose":52}}}}}

代码:

    Stream<Map.Entry<String, String>> flatten(Map<String, Object> map) 
    {
        return map.entrySet()
                .stream()
                .flatMap(this::extractValue);
    }

    Stream<Map.Entry<String, String>> extractValue(Map.Entry<String, Object> entry) {
        if (entry.getValue() instanceof String) {
            return Stream.of(new AbstractMap.SimpleEntry(entry.getKey(), (String) entry.getValue()));
        } else if (entry.getValue() instanceof Map) {
            return flatten((Map<String, Object>) entry.getValue());
        }
        return null;
    }

    @ReactMethod
    public void testFunc(String jsonString, Callback cb){
        Map<String,Object> map = new HashMap<>();

        ObjectMapper mapper = new ObjectMapper();

        try {
            //convert JSON string to Map
            map = mapper.readValue(String.valueOf(jsonString), new TypeReference<Map<String, Object>>() {
            });


            Map<String, String> flattenedMap = flatten(map)
                     .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

            for (Map.Entry<String, String> entry : flattenedMap.entrySet()) {
                Log.e("flatmap",entry.getKey() + "/" + entry.getValue());

                //System.out.println(entry.getKey() + "/" + entry.getValue());
            }
        } catch (JsonParseException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        Log.e("JSONSTRING", jsonString);

        cb.invoke("OK");
    }

首先我想我会用对象映射器将它制作成一个地图,因此我使用对象映射器来获取地图然后我遵循了这种方法 How to Flatten a HashMap?

但是,问题在于结果只给了我orginMerd id和blodid,而不是数字或葡萄糖字段。有没有一种优雅的方法来实现这一目标?不幸的是,我不太精通 Java。

【问题讨论】:

    标签: java android json java-stream


    【解决方案1】:

    粘贴你需要反序列化here的json。选择源类型 JSON,取消选择“允许附加属性”,输入你的包名和你的类名。它会为您生成适合您的 json 的 Java 类(源代码,而不是编译后的 .class 文件)。

    下载生成的源代码,将它们放入您的项目中,然后执行:objectMapper.readValue(string, YourClassName.class);YourClassName 是你在站点中输入的类名(不是MainForm 类,小心,我刚才测试时掉进了那个陷阱)。

    【讨论】:

      【解决方案2】:

      在您当前的版本中,您只允许字符串值。您应该更改它以允许其他简单类型。要确定您可以使用此方法:

      private static boolean isSimpleType(Class<?> type) {
          return type.isPrimitive() ||
                  Boolean.class == type ||
                  Character.class == type ||
                  CharSequence.class.isAssignableFrom(type) ||
                  Number.class.isAssignableFrom(type) ||
                  Enum.class.isAssignableFrom(type);
      }
      

      或查看here 以获取有关如何确定类是否简单的更多详细信息。您也可以简单地调整这些方法以满足您的需求。

      有了这个,您可以使用以下方法来展平您的地图:

      public static Stream<Map.Entry<String, Object>> flatten(Map<String, Object> map) {
          return map.entrySet()
                  .stream()
                  .flatMap(YourClass::extractValue);
      }
      
      private static Stream<Map.Entry<String, Object>> extractValue(Map.Entry<String, Object> entry) {
          if (isSimpleType(entry.getValue().getClass())) {
              return Stream.of(new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue()));
          } else if (entry.getValue() instanceof Map) {
              return flatten((Map<String, Object>) entry.getValue());
          }
          return null;
      }
      

      像以前一样这样称呼它:

      Map<String, Object> flattenedMap = flatten(map)
              .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
      
      flattenedMap.forEach((key, value) -> System.out.println(key + ": " + value));
      

      【讨论】:

        【解决方案3】:

        您只处理字符串类型的值或 json 映射的情况:

        if (entry.getValue() instanceof String) {
            return Stream.of(new AbstractMap.SimpleEntry(entry.getKey(), (String) entry.getValue()));
        } else if (entry.getValue() instanceof Map) {
            return flatten((Map<String, Object>) entry.getValue());
        }
        

        要获取数字或葡萄糖条目,您还应该根据从 JSON 反序列化中获得的信息,使用 LongInteger 处理 Number 类型。

        这种方法的问题在于IntegerLong 不是String,实际上您将json 条目映射到Stream&lt;Map.Entry&lt;String, String&gt;&gt;
        使用toString() 可以将数字放入String 变量中:

        例如同时处理数字和字符串:

        final String value = entry.getValue();
        if (value instanceof String || value instanceof Number) {
            return Stream.of(new AbstractMap.SimpleEntry(entry.getKey(),
                                                        value.toString()));
        }
        else if (value instanceof Map) {
            return flatten((Map<String, Object>) value);
        }
        // you may return null but you will at least log something
        else {
             LOGGER.warn("field with key {} and value {} not mapped", entry.getKey(), value);
             return null;
        }
        

        但它也会使您的地图内容不清楚,需要在使用前进行一些检查。
        同样,使用像 Stream&lt;Map.Entry&lt;String, Object&gt;&gt; 这样的泛型类型也会产生类似的问题。

        所以我认为您可以考虑使用特定的类来表示您的模型并在反序列化期间使用它。

        Foo foo = mapper.readValue(String.valueOf(jsonString), Foo.class);
        

        其中Foo 是一个描述预期结构的类。

        【讨论】:

        • 在这种情况下使用特定类的问题是我正在序列化的 f# 代码是建立在可区分联合之上的,这意味着我需要大量的类来对所有可能性进行建模。
        • 其实现在想想。我需要将此数据插入到 couchbase lite 数据库中,该数据库采用带有字符串对象的地图,所以这几乎正是我想要的。所以谢谢!
        • 但是Map&lt;String, String&gt; 更好吗?它可以利用吗?如果是这样,那就顺其自然吧。编辑:我刚刚看到你的最后一条评论。欢迎你:)
        • 在这种情况下,我使用实现它的代码进行了编辑。
        猜你喜欢
        • 2011-03-04
        • 2017-01-22
        • 2021-01-01
        • 1970-01-01
        • 2019-08-02
        • 1970-01-01
        • 1970-01-01
        • 2011-12-04
        • 1970-01-01
        相关资源
        最近更新 更多