3

I have through various reasons ended up in a situation where I need to deserialize a json object from Fable F# in android studio with java. The string is as follows:

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

the code:

    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");
    }

First I figured I'd make it into a map with object mapper as such I used the object mapper to get a map of then I followed this approach How to Flatten a HashMap?

However the issue with this is that the result only gives me the orginMerd id and the blodid, not the number or glucose fields. Is there a elegant way to achieve this? I am unfortunately not very well versed in Java.

Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
Simplexity
  • 77
  • 1
  • 3

3 Answers3

1

Paste the json you need to deserialize here. Select source-type JSON, deselect 'allow additional properties', input your package name and your class name. It's gonna generate Java classes (source code, not the compiled .class files) for you that fit your json.

Download generated sources, put them into your project and then just do: objectMapper.readValue(string, YourClassName.class);. YourClassName is the class name you input into the site (not MainForm class, be careful, I fell into that trap while testing this just now).

Shadov
  • 5,421
  • 2
  • 19
  • 38
1

In your current version you are only allowing String values. You should change that to allow other simple types. To determine that you can use this method:

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);
}

Or look here for more details on how to determine if a class is simple. You also can simply adjust that methods to fit your needs.

With this you can use the following to flatten your map:

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;
}

And call it like this as before:

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

flattenedMap.forEach((key, value) -> System.out.println(key + ": " + value));
Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
0

You handle only the cases for values of type string or map of your 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());
}

To get number or glucose entries, you should also handle the Number type with Long or Integer according to what you get from the JSON deserialization.

The problem with this approach is that Integer and Long are not String and actually you map the json entries to Stream<Map.Entry<String, String>>.
Putting numbers in String variables is possible with toString() :

For example to handle both number and 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;
}

But it will also make the content of your Map unclear requiring some checks before using it.
Similarly using a generic type like Stream<Map.Entry<String, Object>> will create a similar issue.

So I think that you could consider using a specific class to represent your model and use it during deserialization.

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

where Foo is a class describing the expected structure.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • problem with using a specific class in this instance is that the f# code I am serializing is built up on discriminate unions which means I would need an awful lot of classes to model all the possibilities. – Simplexity Jun 25 '19 at 20:44
  • Actually now that I think about it. I need to insert this data into a couchbase lite database which takes a map with string object so this is pretty much exactly what I want I believe. So thanks! – Simplexity Jun 25 '19 at 20:49
  • But is`Map` better ? And is it exploitable ? If it is the case, goes with that. Edit : I just saw your last comment. You are welcome :) – davidxxx Jun 25 '19 at 20:49
  • In this case I edited with a code that implements it. – davidxxx Jun 25 '19 at 21:05