Does exist an Java query string parser that support nested object ?
For example, I have the following query string :
foo=bar&nested[attr]=found&nested[bar]=false
I would to a java map (Map<String, Object>) like this :
list:
foo => bar
nested => list:
attr => found
bar => false
It will be useful to generate json like this :
{"foo": "bar", "nested": {"attr": "found", "bar": false}}
Yes, there are tons of JSON parsers, the easiest one is JSONSimple, check here:
https://www.mkyong.com/java/json-simple-example-read-and-write-json/
It can handle nested objects (arrays of objects) and a lot of other things. Like you can find on the link, if you need to convert objects to/from JSON consider using something more advanced, like Jackson.
Let's write some code to encode
{
filter: {
make: "honda";
model: "civic";
}
}
into the query string filter.make=honda&filter.model=civic
const { escape } = require("querystring");
function encode(queryObj, nesting = "") {
let queryString = "";
const pairs = Object.entries(queryObj).map(([key, val]) => {
// Handle the nested, recursive case, where the value to encode is an object
itself
if (typeof val === "object") {
return encode(val, nesting + `${key}.`);
} else {
// Handle base case, where the value to encode is simply a string.
return [nesting + key, val].map(escape).join("=");
}
});
return pairs.join("&");
}
Related
I have the following class that uses internally a HashMap:
open class I18n<T> {
var i18n: MutableMap<LanguageEnum, T?> = mutableMapOf()
#JsonAnyGetter get
fun add(lang: LanguageEnum, value: T?) {
i18n[lang] = value
}
// ...
}
Thanks to the #JsonAnyGetter annotation, when I serialize this into Json I have the following format:
{
"pt": "Texto exemplo",
"en": "Example text"
}
instead of
{
i18n: {
"pt": "Texto exemplo",
"en": "Example text"
}
}
Now I need to make the way back. I have a HashMap containing the languages key and I need to desserialize it into my I18n object.
The caveat here is that I'm doing this among a lot of reflections and abstractions and it would be really nice if it could work like this:
// Here I am going through the fields of a given POJO.
// One of those fields is a I18n type.
// My model variable is a Map containing the same keys as my POJO field's name, so I'm basically trying to map them all
for (f in fields) {
if (model.containsKey(f.name)) {
// when f.type is I18n, value is a HashMap<string, string>
val value = model[f.name]
f.isAccessible = true
// when f.type is I18n.class, the field is set to an empty instance of I18n because it could not desserialize
f.set(dto, mapper.convertValue(value, f.type))
f.isAccessible = false
}
}
I would not like to do things like:
if (f.type === I18n.class) {
// Special treatment
}
Any ideas? Thanks in advance.
This is what my class looks like -
public class A {
private Map<String, Object> objects = null;
....
}
My json would be like -
{
"f1" : {
"name" : "some name",
"val" : 3
},
"f2" : {
"arg": {
some field/value pairs
}
}
}
What I want is to specify in the JSON itself the type to which it can be deserialized to. So the value for f1 would be converted to an object of class B and f2 would get converted to object of C.
My code will look like this -
Object o = objects.get("f1");
if (o instanceof B) {
...
} else if (o instanceof C) {
...
}
Is there a way to do this? I want the json to control the deserialization.
Yes, Jackson can use a type identifier if JSON document has it. This is usually done by using annotation #JsonTypeInfo.
There are multiple ways to add/use type identifier, both regarding how it is included in JSON document, and as to what kind of id is being used (type name or Java class name?).
The easiest way to see how things match is to actually start with a POJO, add #JsonTypeInfo annotation, and serialize it to see kind of JSON produced. And once you understood how inclusion works you can modify, if necessary, structure of JSON and/or Java class definition.
Assume I have following DTO:
class C {
String a;
String b;
}
And I have the JSON:
{
"c" : {
"a" : "aaa",
"b" : "bbb"
}
}
What I want to do is, accomplish following test:
C expected = new C("aaa","bbb");
mockMvc.perform(get("url"))
.andExpect(jsonPath("$.c", is(expected)));
It fails. If I first serialize expected to JSON and then try to match, it again fails because it's a string. Is this possible?
Always remember: There is no such thing as a "JSON object". JSON is a serialization format for objects. JSON is always a string. You can convert from object to JSON and back (and hence from object to string and back). But
{ "a": "b" }
is a JavaScript object, not JSON (even if it looks very similar).
This in fact is the answer to your question: When you serialize expected, you get JSON (the transport format, i.e. the string). This isn't what jsonPath() checks. jsonPath() validates against JavaScript types.
This blog post suggests that you need to check each field individually:
.andExpect(jsonPath("$.c.a", is(expected.a)))
.andExpect(jsonPath("$.c.b", is(expected.b)));
which is tedious. What you need is
a) to configure your JSON framework to use a mapping system that sorts keys and
b) you need to figure out what type jsonPath("$.c", ...) returns - it's probably the type which your JSON framework uses to represent generic JavaScript objects.
The check then looks like this:
C c = new C("aaa","bbb");
String serialized = JSON.serialize(c); // to string
JSObject expected = JSON.parse(serialized); // to generic JavaScript object
mockMvc.perform(get("url"))
.andExpect(jsonPath("$.c", is(expected)));
Note that this only works if JSObject has a proper implementation for equals().
If you can afford to modify your "C" class to add it an "equals" operator and to modify slightly your JSON file, I would suggest you to transform your JSON string into an instance of "C". This can be done with a good JSON-ifier (Jackson or GSON). Then you just have to compare the 2 instances.
Some examples with GSON:
class C {
String a;
String b;
public boolean equals(C obj) { return a.equals(obj.a) && b.equals(obj.b); }
}
// Your JSON file should look like that
{
"a" : "aaa",
"b" : "bbb"
}
// So the test is simple
C expected = new C("aaa","bbb");
C json = gson.fromJson(jsonString, C.class);
if (expected.equals(json)) {
// Do whatever you want here
}
If you cannot afford to change the JSON file, just create another class to contains your main class, like this:
class Wrapper {
C c;
}
Wrapper jsonW = gson.fromJson(jsonString, Wrapper.class);
C json = jsonW.c;
...
If you cannot afford the addition of the equals operator, I suggest to create JSON string based on the 2 "C" instance objects and compare the strings. Your jsonString becomes a real "C" object (json) before ending into a new string (jsonStr).
String expectedStr = gson.toJson(expected);
String jsonStr = gson.toJSON(json);
if (expectedStr.equals(jsonStr)) {
// Do whatever you want here
}
How can I pass the output of a method which has multiple level from one class to another in Java . Is there anything which can hold a "tree-like" data apart from tree itself?
for example : if I want to return String data type , the method will be like :
private String method () {
return string; }
What kind of datatype can i use if I want to return an output like the following ?
enter code here
:Thing
:ValuePartition
:Spiciness
:Medium
:DomainConcept
:Food
:IceCream
:Pizza
:NamedPizza
:Mushroom
:AmericanHot
:Caprina
:Margherita
:MeatyPizza
:NonVegetarianPizza
You can use JSON: http://json.org/java/
Your function can return a String (Serialized JSON) or a JSONObject Type.
You JSONObject or String would look something like this.
{
Thing : {
ValuePartition : '',
Spiciness : 'medium',
DomainConcept : '',
Food : ['IceCream','Pizza']
}
}
I'm currently trying to use Flexjson to deserialize a JSON String and map it to the Object model of my
Android App. The application is a kind of library with several vendors that can have some catalogs with more catalogs
and documents in them. The json is fetched from a web service I have no influence on and looks something like this:
{
"library":{
"vendor":[
{
"id":146,
"title":"Vendor1",
"catalog":[
{
"id":847,
"document":[
{
"id":1628,
"title":"Document",
...
},
{
...
}
],
"title":"Catalog ",
},
{
...
}
]
},
{
...
}
]
}
}
So each vendor, catalog, document is represented by a JSONObject and all child catalogues and documents are within a JSONArray.
So far everything works fine with Flexjson and the following deserialization code:
LibraryResponse response = new JSONDeserializer<LibraryResponse>()
.use(Timestamp.class, new TimestampObjectFactory())
.deserialize(getLocalLibrary(), LibraryResponse.class);
return response.library;
I do have a Library object that has a List<Vendor>. Each vendor has a List<Catalog> and a List<Document>.
But unfortunately, the web service straps the JSONArrays to simple JSONObjects if a catalog contains only a single document
or a catalog contains just one catalog. So the json in that case looks like this:
"document":
{
"id":1628,
"title":"Document",
...
}
Now Flexjson doesn't know how to deserialize and I end up with a library.vendorX.getDocument() being a List<HashMap> instead of a List<Document>.
One idea is to tell Flexjson explicitly how to handle such cases, but I have no idea where to start with this. Another way could be to parse the initial json manually and replace such JSONObjects with the appropriate JSONArray. But I think that way is not really nice to go, as the library can be pretty deep.
I hope you can provide some guidance here.
Yikes this is some gnarly json mapping going on. What backend coder did that?! #NotHelping.
Well from looking at the code, Flexjson is coded to handle this out of the box. But it looks like it's not passing the typing information down to the bind so it doesn't know what type it's binding into so it just returns a Map. That's a bug that should probably be fixed. Good news is there is a work around.
Anyway, the simplest thing I can think of is to install an ObjectFactory on that list. Then you can check and see if you get a Map or a List when the stream is deserialized. Then you can wrap it in a List and send it on to the appropriate decoder. Something like:
LibraryResponse response = new JSONDeserializer<LibraryResponse>()
.use(Timestamp.class, new TimestampObjectFactory())
.use("library.vendor.values.catalog.values.document", new ListDocumentFactory() )
.deserialize(getLocalLibrary(), LibraryResponse.class);
Then
public class ListDocumentFactory implements ObjectFactory {
public Object instantiate(ObjectBinder context, Object value, Type targetType, Class targetClass) {
if( value instanceof Collection ) {
return context.bindIntoCollection((Collection)value, new ArrayList(), targetType);
} else {
List collection = new ArrayList();
if( targetType instanceof ParameterizedType ) {
ParameterizedType ptype = (ParameterizedType) targetType;
collection.add( context.bind(value, ptype.getActualTypeArguments()[0]) );
} else {
collection.add( context.bind( value ) );
return collection;
}
}
}
}
I think that's roughly what would fix that bug, but should also fix your problem.