Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How load an object from son without @type #122

Closed
wolfviking0 opened this issue Oct 27, 2017 · 9 comments
Closed

How load an object from son without @type #122

wolfviking0 opened this issue Oct 27, 2017 · 9 comments

Comments

@wolfviking0
Copy link

Hi,

I am playing around with json-io, and I have an issue to create my java object. If I export my Java Object using JsonWriter.objectToJson, the JsonReader work fine, but because the json generated has the key @type.

Unfortunately in my test I need to load a json not generated by json-io and I do not have the @type inside, but I know the type of the class I want. I tried different way using the map argument but so far I was not able to solve it.

Any advice will be really nice.

Thanks

@jdereg
Copy link
Owner

jdereg commented Nov 11, 2017

When writing JSON, you can set a flag to remove the @type from the output. Another option is to leave it in, and use the JsonReader mode that reads JSON into a Map of Maps representation.

The JsonWriter option is JsonWriter.TYPE. Place that as a key in a Map and associated it to the value false:

Map options = new HashMap();
options.put(JsonWriter.TYPE, false);
JsonWriter.objectToJson(root, options);

To read any JSON into memory, use:

Map doc = JsonReader.jsonToMaps(root);

Optionally, you can also use:

Map options = new HashMap();
options.put(JsonReader. USE_MAPS, true);
Object doc = JsonReader.jsonToJava(root, options);

See the user guide (link on main json-io page) for detailed instructions and examples.

@wolfviking0
Copy link
Author

Hi, yes generate the json without type was ok.
But my goal it's to be able to load some generated JSON (not using java), and be able to reconstruct a java object, be able to do something like MyObject obj = JsonReader(jsonstring, MyObject.class).

Regarding the response of using Map of Maps, do you think that could be enough to reconstruct the object using reflection ?

Thanks

@jdereg
Copy link
Owner

jdereg commented Nov 11, 2017

Try both of the options I listed for reading - one of those two will be very likely what you want.

@wolfviking0
Copy link
Author

Ok Let me try again, I will let you know

@wolfviking0
Copy link
Author

Hi @jdereg,

So far I think I understand perfectly you sample. And I think it's not possible to achieve what I want.
After having the Object or the Maps, I want to retrieve the real object and it seems not possible.

Here in your sample you are using a map. In my case the Map it's just a standard java object.

{
"a":"alpha",
"b":[
{
"a":"beta",
"b":[],
"c":{
"name":"BETA"
}
}
],
"c":{
"name":"ALPHA"
}
}

The json it's ok like that, but know I want to reconstruct the object from this json.

@jdereg
Copy link
Owner

jdereg commented Nov 15, 2017

If you output with the @type, then it will be constructed as a Java object. Json-io can serialize/deserialize any Java object graph. To do this, it puts @type on objects that need the hint so that it reconstructs the proper instance. Note that the extra @type field is benign when sent to Javascript.

@jdereg jdereg closed this as completed Nov 22, 2017
@ozmium
Copy link

ozmium commented Jan 5, 2019

I am also having difficulty with this. I would like convert a simple string like "[10,20,30,40]" to an ArrayList<Integer>. The json-io library does not seem to support it:

@Test
public void testArrayListSaveAndRestoreGenericJSON() {

    ArrayList<Integer> numbers = new ArrayList<>();
    numbers.add(10);
    numbers.add(20);
    numbers.add(30);
    numbers.add(40);

    // Serialize the ArrayList to Json
    HashMap disableTypes = new HashMap();
    disableTypes.put(JsonWriter.TYPE, false);
    String json = JsonWriter.objectToJson(numbers, disableTypes);

    System.out.println("Numbers ArrayList = " + numbers + ". Numbers to json = " + json);
    // This prints: "Numbers ArrayList = [10, 20, 30, 40]. Numbers to json = [10,20,30,40]"

    // Deserialize the Json string to ArrayList
    HashMap enableTypes = new HashMap();
    enableTypes.put(JsonWriter.TYPE_NAME_MAP, "java.util.ArrayList");
    ArrayList<Integer> restoredNumbers;
    restoredNumbers = (ArrayList<Integer>) JsonReader.jsonToJava(json); // Not working - ClassCastException
    restoredNumbers = (ArrayList<Integer>) JsonReader.jsonToJava(json, enableTypes); // Also not working - ClassCastException
    System.out.println("restoredNumbers = " + restoredNumbers);

    assertThat(numbers, is(restoredNumbers));
}

How do I convert the json string "[10,20,30,40]" back into an ArrayList<Integer> ? I can't figure it out.

When using the normal JsonWriter, it writes very verbose strings, like this for the sameArrayList<Integer> :

"Numbers to json = {"@type":"java.util.ArrayList","@items":[{"@type":"int","value":10},{"@type":"int","value":20},{"@type":"int","value":30},{"@type":"int","value":40}]}"

So I want to avoid the long strings, but also deserialize them.

@jdereg
Copy link
Owner

jdereg commented Jan 10, 2019

The JSON [10, 20, 30, 40] should return an Object[], not an ArrayList.

One other tip: When referencing types, you should use the highest possible interface rather than a concrete class like ArrayList. An example:

Map<String, Object> namesToAge = new HashMap<>()

as opposed to:?

HashMap<String, Object> namesToAge = new HashMap<>()

Why? Because the first approach only defines the concrete type HashMap in one place, and all other places reference it simply as Map. If later, you decide to switch to TreeMap, you only have to change your code in one place.

For all method parameters, variable type definitions, you should use the interface, not the concrete type. And you should use the interface that is 'highest up' the hierarchy with the least amount of methods on it. Why? Because the higher-up the hierarchy of the interface used, the more options you have for the actual instance (concrete type) used.

@ozmium
Copy link

ozmium commented Jan 10, 2019

@jdereg Thanks for the suggestion about using Interface types.

Maybe I'm wrong, but isn't there also a downside to that approach...?

  1. Increased number of Imports: You now need to import 2 classes instead of 1: java.util.Map and java.util.HashMap
  2. If I know that the implementation won't change, do I need to risk over-engineering the code with Interfaces?
  3. TreeMap and HashMap have similar methods, being descendants of AbstractMap. Switching between the 2 types should be easy anyway, even if I have to change a few methods.
  4. It requires extra casts when using child-specific methods. Example:
List<Integer> numbersList = new ArrayList<>();
ArrayList<Integer> numbersArrayList = new ArrayList<>();

// Call trimToSize() on the lists
((ArrayList<Integer>) numbersList).trimToSize(); // Cast needed, more verbose
numbersArrayList.trimToSize();  // No cast needed

I'm confused why the Json-IO library allows JsonWriter to output generic JSON like "[10, 20, 30, 40]", but JsonReader cannot read it back in to the original object type. Is it possible to clarify this in the documentation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants