20

I have a string that is in Json format, only none of the keys or values are surrounded by quotes. For example, I have this:

String json = "{name: Bob, state: Colorado, Friends: [{ name: Dan, age: 23 }, {name: Zane, age: 24 }]}"

I want this to become a map that looks like so:

Map<String, Object> friend1Map = new HashMap<>();
friend1Map.put("name", "Dan");
friend1Map.put("age", 23);

Map<String, Object> friend2Map = new Hashmap<>();
friend2Map.put("name", "Zane");
friend2Map.put("age", 24);

Map<String, Object> newMap = new HashMap<>();
newMap.put("name", "Bob");
newMap.put("state", "Colorado");
newMap.put("Friends", Arrays.asList(friend1Map, friend2Map));

I have tried the following two methods:

ObjectMapper mapper = new ObjectMapper();
mapper.readValue(json, new TypeReference<Map<String, Object>>() {});

This will throw an error, saying:

Unexpected character ('n'): was expecting double-quote to start field name

Then I tried changing the config of mapper:

mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mapper.readValue(json, new TypeReference<Map<String, Object>>() {});

But this threw an error saying:

com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Bob': was expecting ('true', 'false' or 'null')
at [Source: {name: Bob, state: Colorado, Friends: [{ name: Dan, age: 23 }, {name: Zane, age: 24 }]}; line: 1, column: 11]

Is there a way of getting this Map when quotes aren't included in the json string?

user2869231
  • 1,431
  • 5
  • 24
  • 53
  • 1
    Your Json String is invalid, test it here : http://jsonlint.com – Benoit Vanalderweireldt Apr 12 '16 at 19:59
  • how so? and apparently this comment needs 15 characters in order to add it – user2869231 Apr 12 '16 at 20:01
  • or rather, how would I make it valid? Clearly the issue is in not having the quotes. Would I have to do some nasty splits? – user2869231 Apr 12 '16 at 20:04
  • 2
    Just to be clear: that is not Json, just Json-like. I dont think jackson can handle unquoted sring values, only unquoted keys are supported. This answer might help you: http://stackoverflow.com/a/34794445/5108418 – Tamas Hegedus Apr 12 '16 at 20:05
  • 1
    You might look into the GSON library rather than Jackson. See http://stackoverflow.com/questions/20557131/gson-parse-unquoted-value – unigeek Apr 12 '16 at 20:10
  • dang. Well after inspection, the String comes from using a mapper.writeValueAsString(map). That map is essentially the one I'm aiming to get. So to get around this problem, I should be able to replace the writeValueAsString with something that will escape the quotes inside of the map when writing it as a string. Is there a way of doing that? – user2869231 Apr 12 '16 at 20:52
  • This json looks like yaml and could be parsed as such – Roman Nov 12 '21 at 20:54

6 Answers6

4

SINCE GSON v2.8.6

ObjectMapper with jackson fasterxml doesn't support values without quotes, but GSON does:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonParser;

.

  JsonNode json = json("{"
      + "  name: Bob,      "
      + "  state: Colorado,"
      + "  Friends: [      "
      + "    {"
      + "      name: Dan,  "
      + "      age: 23     "
      + "    },"
      + "    {"
      + "      name: Zane, "
      + "      age: 24     "
      + "     }"
      + "  ],"
      + "  extra: \"text with spaces or colon(:) must be quoted\""
      + "}");

  Map m = new ObjectMapper().convertValue(json, Map.class);

.

JsonNode json(String content) throws IOException {

  String canonicalFormat = JsonParser.parseString(content).toString();
  return json.readTree(canonicalFormat);
}

EARLIER GSON

Before v2.8.6 GSON didn't have the static parseString method. So you should use the (deprecated in higher versions) instance method:

JsonNode json(String content) throws IOException {

  String canonicalFormat = new JsonParser().parse(content).toString();
  return json.readTree(canonicalFormat);
}

SINCE Java 15

NOTE: we expect Java 15 will support unescaped double-quotes as is:

var json = """
        {"name": "Bob", "state": "Colorado", "Friends": [{ "name": "Dan", "age": 23 }, {"name": "Zane", "age": 24 }]} """;

more details

epox
  • 9,236
  • 1
  • 55
  • 38
2

To answer your question: There is no safe way of getting your JSON - which is not JSON by the way, because it's invalid - converted into a Map<String, Object>.

Let me elaborate just a little bit (why can't it be parsed safely?): Imagine a "JSON" like this:

{
    this is my key: and here's my value and it even has a colon:
}

Valid JSON would look something like this

{
    "this is my key": "and here's my value and it even has a colon:"
}

With the quotes a parser can safely determine keys and values. Without them a parser is lost.

Brian
  • 872
  • 8
  • 16
  • we all know it is not valid but nevertheless we are asking because the telemetry messages we are receiving come without quotes. – mrki102 Nov 17 '20 at 18:33
  • @mrki102, then I suggest that you implement a "filter" which corrects the incoming data. Then you can choose any parser you like. The filter could use a little help by defining some sort of schema to let it know which field _values_ are strings, which need quotation in addition to field keys/labels. – Brian Nov 19 '20 at 08:08
1

The ObjectMapper won't be able to parse the Json input if it's not valid, so the answer is no.

Test your Json here : http://jsonlint.com/

After correcting the Json input you could do :

    ObjectMapper ob = new ObjectMapper();
    ob.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
    String json = "{name: \"Bob\", state: \"Colorado\", Friends: [{ name: \"Dan\", age: 23 }, {name: \"Zane\", age: 24 }]}";

    Map<String, ObjectNode> theMap = ob.readValue(json, Map.class);
    System.out.println(theMap.get("name"));

Will print :

Bob

Benoit Vanalderweireldt
  • 2,925
  • 2
  • 21
  • 31
  • 4
    Jackson can parse invalid JSON. Notice the `JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES`. It just can't parse unquoted field values (it seems). – Savior Apr 12 '16 at 20:05
  • Also, your `theMap` will not have only `String` values. There are arrays in that JSON. What you've proposed is not type safe. Use an `ObjectNode` instead. – Savior Apr 12 '16 at 20:07
  • That's not what I meant. Not all the values are `ObjectNode` either. I meant more of `ObjectNode root = ob.readValue(json, ObjectNode.class);` – Savior Apr 12 '16 at 21:01
  • Well he asked how to convert a Json String into a Map – Benoit Vanalderweireldt Apr 12 '16 at 21:25
  • He also asked to do it with unquoted values. You corrected that part. You should correct the rest as well. There's no way to have a type safe parameterized `Map` reference here. You could have a `Map>` or `Map` but that's pretty useless. `ObjectNode` gives you much more flexibility. – Savior Apr 12 '16 at 21:29
1

you may could use the following code to preprocess the json string and then parse it.

String str = ("{requestid:\"55275645.f213506045\",timestamp:\"213506045\",bidnumber:\"55275645\",checkcode:\"1f6ad033de64d1c6357af34d4a38e9e3\",version:\"1.0\",request:{token:ee4e90c2-7a8b-4c73-bd48-1bda7e6734d5,referenceId:0.5878463922999799}}");
    str = (str.replaceAll(":\"?([^{|^]*?)\"?(?=[,|}|\\]])",":\"$1\"").replaceAll("\"?(\\w+)\"?(?=:)","\"$1\""));
    JSONObject obj = JSON.parseObject(str);
    System.out.println(obj.getJSONObject("request").getString("token"));
张凡杭
  • 19
  • 1
0

We can convert such a json to a Map using Apache Commons and Guava base libraries. Example: Input - {name=jack, id=4} Output - Map(name = jack, id = 4)

import com.google.common.base.Splitter;
import org.apache.commons.lang.StringUtils;

String input = StringUtils.substringBetween(value, "{", "}");
Map<String, String> map = 
Splitter.on(",").withKeyValueSeparator("=").split(input);
Dhruv
  • 149
  • 4
0
ObjectMapper om=new ObjectMapper();
om.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);

List<RootMember> outputList;
try {
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(resp); 
    outputList = om.readValue(json, new TypeReference<List<RootMember>>(){});
} catch (JsonMappingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (JsonProcessingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}