Return HashMap<String, Object> from GraphQL-Java
Asked Answered
T

4

20

I tried few variant and had no luck to return a map in GraphQL. So I have the following two objects:

public class Customer {

    private String name, age;
    // getters & setters
}

public class Person {

   private String type;
   private Map<String, Customer> customers;
   // getters & setters
}

My schema looks like this:

type Customer {
   name: String!
   age:  String!
}

type Person {
  type: String!
  customers: [Customer!] // Here I tried all combination but had no luck, is there a Map type support for GQL?
}

Can someone please tell me how to achieve this so that GraphQL magically process this or an alternative approach.

Many thanks!

This answered 6/12, 2017 at 12:41 Comment(0)
T
0

Just in case - you can always represent map object as a JSON string (in my case it was helpful).

public class Person {

    private String type;
    private Map<String, Customer> customers;
    // getters & setters
}

Would be

type Person {
  type: String!
  customers: String!
}

After that don't forget to add data fetcher to convert it to the JSON.

public DataFetcher<String> fetchCustomers() {
        return environment -> {
            Person person = environment.getSource();
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                return objectMapper.writeValueAsString(person.getCustomers());
            } catch (JsonProcessingException e) {
                log.error("There was a problem fetching the person!");
                throw new RuntimeException(e);
            }
        };
    }

It'll return:

"person": {
    "type": "2",
    "customers": "{\"VIP\":{\"name\":\"John\",\"age\":\"19\"},\"Platinum VIP\":{\"name\":\"Peter\",\"age\":\"65\"}}"
  }

After that, you can operate with customers as with typical JSON string in your client.

Tocantins answered 26/11, 2019 at 2:18 Comment(0)
O
15

As you yourself noted, there's no map type in GraphQL, mostly because maps are data with dynamic structure and, as such, do not translate well into static types that GraphQL expects. Still, you have a few options.

  1. You could change the value type so it includes the key, and give up on the map and use a list instead. This is the approach you took in your own answer. I won't go into detail here as you've already exemplified it.

  2. As long as the key and value Java types are known (and not e.g. Object), you can treat a map as list of key-value pairs. You can create a type to represent the pair:

    type Person { type: String! customers: [CustomerEntry!] }

    type CustomerEntry { key: String! value: Customer! }

On the down side, you now have uglier queries:

{
   person {
     type
     customers {
       key
       value {
         name
       }
     }
   }
}

On the up side, you keep type safety and (mostly) the semantics. It is possible to keep nesting this approach to e.g. represent a Map<String, Map<Long, Customer>>.

  1. If you ever have a completely unknown type, i.e. Object, the only option is to treat it as a complex scalar. In JavaScript, this approach is known as JSON scalar as it boils down to stuffing an arbitrary JSON structure in and treating it as a scalar. The same approach can be implemented in Java. graphql-java now has a project for extended scalars. Here's their ObjectScalar (aliased as JsonScalar) implementation.

Now, if you want to represent a type such as Map<String, Object>, you can opt to represent it using the key-value pair approach from above, with only the value type being the JSON scalar, or you can represent the entire map as a JSON scalar.

As a matter of fact, you can decide to represent any map (well, any type really, but that's not useful) as a JSON scalar.

type MapEntry {
  key: String!
  value: [ObjectScalar!]
}

scalar ObjectScalar

On the upside, you can now keep any dynamic structure's shape exactly. On the downside, since it is a scalar, it is impossible to make sub-selections, and you're stuck fetching it all, without knowing what's inside in advance.

Outrageous answered 27/1, 2018 at 4:32 Comment(13)
"mostly because maps are basically untyped data". I disagree. IMO, a map is as typed as an array is.Mammoth
How about set / hashset?Midlothian
@KokHowTeh Nothing special needed there, just represent it as a list in GraphQL.Outrageous
@kaqqao, how do you ensure uniqueness of the items then?Midlothian
@KokHowTeh Why would GraphQL be doing that? Java does that.Outrageous
I've tested your 2) option - but it does not work. See: pastebin.com/riaCkDmu for code. I get this error: type mismatch error, expected type LIST got class java.util.HashMap I ask for it like this: query { getSimpleMapExample { key value } }Betrothal
@yami Your fetcher is returning a Map where a list is expected. There's no magic that will do the conversion for you - you have to do it yourself in the fetcher.Outrageous
ok - so you meant above to create two Lists in the code one for keys and second for values right?Betrothal
@yami You need a List of entries, something like a List<Map.Entry<K,V>>. See e.g. this implementation.Outrageous
Ok - now i receive null null as key and value pastebin.com/BQT4syMp do you may know why is that ? In debug i see that the method is executed and returns list of one map. I ask for it like this: query { getSimpleMapExample { key value } } Betrothal
@yami Not a list of maps, a list of entries. I really can't help you further.Outrageous
Caused by: java.lang.ClassCastException: class graphql.kickstart.tools.util.ParameterizedTypeImpl cannot be cast to class java.lang.Class (graphql.kickstart.tools.util.ParameterizedTypeImpl is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')Sublittoral
#68595417 input WorkspaceInput{ metadata: WorkspaceMetadata! entities: [Entities!] permissionList: [Permissions]! } input Entities { key: Domain value: EntityType! } enum Domain { PRODUCT_BUILDER } input EntityType { key: String value: EntityKey } input EntityKey { key: String defPackageKey: String entityId: Int entityNumber: EntityNumber } enum EntityNumber { PRODUCT, PRODUCT_VERSION, PRODUCT_VERSION_LOB }Sublittoral
T
3

There is no map type in GraphQL (Discussion on GitHub).

An alternative approach would be to have customers as a List of Customers

public class Person {
   private String type;
   private List<Customer> customers;
}

and include the key for the map inside the Customer class

public class Customer {
    private String key; // or another meaningful name
    private String name, age;
}

Schema would mostly remain the same.

type Customer {
   key: String! // or another meaningful name
   name: String!
   age: String!
}

type Person {
  type: String!
  customers: [Customer!]!
}
Theocrasy answered 7/12, 2017 at 17:1 Comment(0)
T
0

Just in case - you can always represent map object as a JSON string (in my case it was helpful).

public class Person {

    private String type;
    private Map<String, Customer> customers;
    // getters & setters
}

Would be

type Person {
  type: String!
  customers: String!
}

After that don't forget to add data fetcher to convert it to the JSON.

public DataFetcher<String> fetchCustomers() {
        return environment -> {
            Person person = environment.getSource();
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                return objectMapper.writeValueAsString(person.getCustomers());
            } catch (JsonProcessingException e) {
                log.error("There was a problem fetching the person!");
                throw new RuntimeException(e);
            }
        };
    }

It'll return:

"person": {
    "type": "2",
    "customers": "{\"VIP\":{\"name\":\"John\",\"age\":\"19\"},\"Platinum VIP\":{\"name\":\"Peter\",\"age\":\"65\"}}"
  }

After that, you can operate with customers as with typical JSON string in your client.

Tocantins answered 26/11, 2019 at 2:18 Comment(0)
S
0

we've got three options:

1)Return as JSON String 2)Use GraphQL custom scalar type 3)Return as List of key-value pairs

https://www.baeldung.com/java-graphql-return-map

Shipwreck answered 22/8, 2022 at 5:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.