0

Right now I'm trying to parse incoming JSON that is in this format:

{
  <email>: {
    <name>: <string>,     # setting value
     ...
  },
  ...
}

For example:

{
 "aaa@example.com": {
   "statement": true
 },
 "bbb@example.com": {
   "statement": false
 }
}

I also will not know how many emails will be in this JSON. I am a little befuddled as to how you could get all these emails with Jackson without knowing the property name for this, and I was wondering if it was possible.

Here is my code so far:

public class GDPRConsent extends Model {
@JsonIgnore
private static final String GDPR_CONSENT = "gdprConsent";

private Map<String, Object> additionalProperties = new HashMap<String, Object>();

@JsonProperty
private ArrayList<String> emails;

@JsonProperty("serviceDataCollection")
private String dataCollection;

@JsonProperty("serviceDataCollection")
public String getDataCollectionConsent() {
    return dataCollection;
}

@JsonProperty
public ArrayList<String> getEmails() {
    return emails;
}

@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
    return this.additionalProperties;
}

@Override
public String getId() {
    return GDPR_CONSENT;
}

}

Here is my parser:

public static <T> T parseObject(String sourceJson, Class<T> classToParse) {
    T parsedObject = null;
    try {
        parsedObject = sObjectMapper.readValue(sourceJson, classToParse);
    } catch (JsonParseException e) {
        LogUtils.d(LOG_TAG, "parseObject JsonParseException: " + e.toString());
    } catch (JsonMappingException e) {
        LogUtils.d(LOG_TAG, "parseObject JsonMappingException: " + e.toString());
    } catch (IOException e) {
        LogUtils.d(LOG_TAG, "parseObject IOException: " + e.toString());
    }
    return parsedObject;
}

I am currently getting an empty result returned even though I know the JSON is being passed in.

Josh
  • 386
  • 5
  • 14

2 Answers2

1

If your JSON only includes the data given in your example, then it corresponds to a TypeReference<Map<String, Map<String, Boolean>>>, which is basically a mapping of strings to a mapping of strings to booleans. An example parser looks like this (no extra POJOs required):

import java.io.IOException;
import java.util.Map;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JSONParser {

    static final String TEST_JSON = "{"
            +" \"aaa@example.com\": {"
            +"  \"statement\": true"
            +"},"
            +"\"bbb@example.com\": {"
            +"  \"statement\": false"
            +"}"
            +"}";


    public static void main (String... args) {

        ObjectMapper mapper = new ObjectMapper();

        try {
            Map<String, Map<String, Boolean>> jsonAsNestedMap = mapper.readValue(
                    TEST_JSON, new TypeReference<Map<String, Map<String, Boolean>>>() {
            });
            System.out.println(jsonAsNestedMap);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }        
    }

}

This will print out

{aaa@example.com={statement=true}, bbb@example.com={statement=false}}

If the innermost values of your JSON are more complex, then you can use TypeReference<Map<String, Map<String, Object>>>:

static final String TEST_JSON = "{"
            +" \"aaa@example.com\": {"
            +"  \"statement\": true,"
            +"  \"another_property\" : \"value 1\"" 
            +"},"
            +"\"bbb@example.com\": {"
            +"  \"statement\": false,"
            +"  \"another_property\" : \"value 2\"" 
            +"}"
            +"}";    
//...    
public static void main (String... args) {
    //...
    Map<String, Map<String, Object>> jsonAsNestedMap = mapper.readValue(
                    TEST_JSON, new TypeReference<Map<String, Map<String, Object>>>() {

        });
//...
}

Accessing individual properties is possible through normal map iteration and accessor methods:

for (Entry<String, Map<String, Object>> e : jsonAsNestedMap.entrySet()) {
    System.out.println("email:" + e.getKey() + ", another_property: " 
        + e.getValue().get("another_property")); 
}

which would give

email:aaa@example.com, another_property: value 1
email:bbb@example.com, another_property: value 2
Mick Mnemonic
  • 7,808
  • 2
  • 26
  • 30
  • Hey Mick, do you know how to do it in the way that the other answer proposed and how I have it in my post, which is the having it as its own class with @JsonPropertys and whatnot. Is it possible to do it that way? – Josh Jul 21 '18 at 04:32
  • You could have a look at [this question](https://stackoverflow.com/questions/17685508/jackson-deserialization-with-unknown-dynamic-properties) and the `JsonAnySetter` approach (also mentioned by Andreas). I can try to come up with another complete example later today. – Mick Mnemonic Jul 21 '18 at 09:53
0

I'm trying to parse incoming JSON that is in this format

As already explained in your duplicate question, you can parse into a Map.

public class EmailData {
    private boolean statement;
    public boolean isStatement() {
        return this.statement;
    }
    public void setStatement(boolean statement) {
        this.statement = statement;
    }
    @Override
    public String toString() {
        return "EmailData[statement=" + this.statement + "]";
    }
}

Test

String json = "{" +
                "\"aaa@example.com\": {" +
                  "\"statement\": true" +
                "}," +
                "\"bbb@example.com\": {" +
                  "\"statement\": false" +
                "}" +
              "}";

ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, EmailData>> typeRef = new TypeReference<>() {/**/};
HashMap<String, EmailData> emails = mapper.readValue(json, typeRef);
System.out.println(emails);

Output

{aaa@example.com=EmailData[statement=true], bbb@example.com=EmailData[statement=false]}

If you prefer the @JsonAnySetter approach, you can do something like this:

public class Content {
    private List<EmailData> emailData = new ArrayList<>();
    @JsonAnySetter
    public void addEmail(String name, EmailData value) {
        value.setEmail(name);
        this.emailData.add(value);
    }
    @Override
    public String toString() {
        return this.emailData.toString();
    }
}
public class EmailData {
    private String email;
    private boolean statement;
    @JsonIgnore
    public String getEmail() {
        return this.email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public boolean isStatement() {
        return this.statement;
    }
    public void setStatement(boolean statement) {
        this.statement = statement;
    }
    @Override
    public String toString() {
        return "EmailData[email=" + this.email + ", statement=" + this.statement + "]";
    }
}

Test

String json = "{" +
                "\"aaa@example.com\": {" +
                  "\"statement\": true" +
                "}," +
                "\"bbb@example.com\": {" +
                  "\"statement\": false" +
                "}" +
              "}";

ObjectMapper mapper = new ObjectMapper();
Content content = mapper.readValue(json, Content.class);
System.out.println(content);

Output

[EmailData[email=aaa@example.com, statement=true], EmailData[email=bbb@example.com, statement=false]]
Andreas
  • 154,647
  • 11
  • 152
  • 247