[SPIGOT-1325] Deserialization of ItemStack fails. Created: 05/Dec/15  Updated: 21/Oct/20  Resolved: 06/Dec/15

Status: Resolved
Project: Spigot
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Minor
Reporter: Taylor Assignee: Unassigned
Resolution: Fixed Votes: 0
Labels: Inventory, bug
Environment:

Linux Ubuntu 14.04LTS


Issue Links:
Cloners
is cloned by SPIGOT-1343 Deserialization of ItemStack fails wh... Closed

 Description   

When calling ItemStack.deserialize, a casting exception occurs which breaks deserialization.

Unable to find source-code formatter for language: text. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at java.lang.Thread.run(Thread.java:745)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at org.bukkit.craftbukkit.v1_8_R3.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:53)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at org.bukkit.craftbukkit.v1_8_R3.scheduler.CraftTask.run(CraftTask.java:71)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at com.lumengaming.giftwrap.service.DataService$$Lambda$16/912274127.run(Unknown Source)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at com.lumengaming.giftwrap.service.DataService.lambda$getPackageById$6(DataService.java:37)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at com.lumengaming.giftwrap.repository.MySqlDataRepository.getPackageById(MySqlDataRepository.java:131)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at com.lumengaming.giftwrap.repository.MySqlDataRepository.readPackages(MySqlDataRepository.java:158)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at com.lumengaming.giftwrap.SerializerUtility.fromJson(SerializerUtility.java:88)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at com.lumengaming.giftwrap.SerializerUtility.deserialize(SerializerUtility.java:139)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: 	at org.bukkit.inventory.ItemStack.deserialize(ItemStack.java:533)
05.12 22:57:34 [Server] INFO [22:57:34 WARN]: java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
05.12 22:57:12 [22:57:12 INFO]: Pangamma ran command Message of the Day
05.12 22:57:12 [Connect] User [22:57:12 INFO]: Pangamma, IP 127.0.0.1
05.12 22:57:12 [Server] INFO [22:57:12 INFO]: UUID of player Pangamma is f77b713b-7aa0-3854-8208-1b3550c188ca
05.12 22:53:28 [Se

The fix is to make sure the amount is being cast to Number, then the .intValue() method is used to get the int value of the amount.

if (args.containsKey("amount")) {
            amount = ((Number) args.get("amount")).intValue();
}


 Comments   
Comment by kamcio96 [ 21/Oct/20 ]

@md_5 fixed in code? I can't find related commit

Comment by Taylor [ 06/Dec/15 ]

You rock, man. Good job!

Comment by md_5 [ 06/Dec/15 ]

Issue was fixed.

Comment by Taylor [ 06/Dec/15 ]

@md_5

What alternative would you suggest instead of the current solution then? There might be some already-defined method I'm overlooking right now.

Comment by Taylor [ 06/Dec/15 ]

I'm using google gson to parse the json into the map types... Then ItemStack.deserialize from there... Warning: Prototype code that looks terrible and will need to be refactored later coming in:

package com.lumengaming.giftwrap;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Material;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;


public class SerializerUtility {
	
	//<editor-fold defaultstate="collapsed" desc="Second Attempt">
	public static String toJson(final ItemStack[] is){
		String json = "";
		try{		
			List<TreeMap<String, Map<String, Object>>> data = serialize(is);
			Gson gson = new GsonBuilder().serializeNulls().create();
			json = gson.toJson(data);
//			System.out.println(json);
//			File f = new File("tester.bin");
//			FileOutputStream fos;
//			fos = new FileOutputStream(f);
//			fos.write(json.getBytes());
		}
		catch (Exception ex){
			Logger.getLogger(SerializerUtility.class.getName()).log(Level.SEVERE, null, ex);
		}
		return json;
	}
	
	public static String toJsonSingle(final ItemStack is){
		String json = "";
		try{		
			List<TreeMap<String, Map<String, Object>>> data = serialize(new ItemStack[]{is});
			Gson gson = new GsonBuilder().serializeNulls().create();
			json = gson.toJson(data.get(0));
		}
		catch (Exception ex){
			Logger.getLogger(SerializerUtility.class.getName()).log(Level.SEVERE, null, ex);
		}
		return json;
	}
	
	public static ItemStack fromJsonSingle(final String json){
		ItemStack is  = null;
		try{		
			Gson gson = new GsonBuilder().serializeNulls().create();
//			ArrayList<TreeMap<String, Map<String, Object>>>
			TypeToken tt = new TypeToken<TreeMap<String, Map<String, Object>>>(){};
			Type collectionType =  tt.getType();
			TreeMap<String, Map<String, Object>> fromJson = gson.fromJson(json, collectionType);
			ArrayList<TreeMap<String, Map<String, Object>>> list = new ArrayList<>();
			list.add(fromJson);
			ItemStack[] deserialize = deserialize(list);
			return deserialize[0];
		}
		catch (Exception ex){
			Logger.getLogger(SerializerUtility.class.getName()).log(Level.SEVERE, null, ex);
		}
		return is;
	}
	
	public static ItemStack[] fromJson(final String json){
		ItemStack[] is  = null;
		try{		
			Gson gson = new GsonBuilder().serializeNulls().create();
//			ArrayList<TreeMap<String, Map<String, Object>>>
			TypeToken tt = new TypeToken<ArrayList<TreeMap<String, Map<String, Object>>>>(){};
			Type collectionType =  tt.getType();
			List<TreeMap<String, Map<String, Object>>> fromJson = gson.fromJson(json, collectionType);
			return deserialize(fromJson);
		}
		catch (Exception ex){
			Logger.getLogger(SerializerUtility.class.getName()).log(Level.SEVERE, null, ex);
		}
		return is;
	}
	
	private static List<TreeMap<String,Map<String,Object>>> serialize(final ItemStack[] itemStackList) {
		final List<TreeMap<String,Map<String,Object>>> serialized = new ArrayList<>();
		for (ItemStack itemStack : itemStackList) {
			TreeMap<String,Map<String,Object>> sMap = new TreeMap<>();
			ItemMeta im = null;
			
			
			try{
				if (itemStack == null){
					itemStack = new ItemStack(Material.AIR);
				}
				im = itemStack.hasItemMeta() ? itemStack.getItemMeta() : null;
				Map<String, Object> serializedItemMeta = (im != null) ? im.serialize() : null;
				sMap.put("ItemMeta",serializedItemMeta);
			}catch(Exception ex){
				ex.printStackTrace();
			}
			
			itemStack.setItemMeta(null);
			
			try{
				Map<String, Object> serializedItemStack = itemStack.serialize();
				sMap.put("ItemStack",serializedItemStack);
			}catch(Exception ex){
				sMap.put("ItemStack", new ItemStack(Material.BARRIER).serialize());
				if (sMap.containsKey("ItemMeta")){
					sMap.remove("ItemMeta");
				}
				ex.printStackTrace();
			}
			itemStack.setItemMeta(im);
			serialized.add(sMap);
		}
		return serialized;
	}
		
	private static ItemStack[] deserialize(final List<TreeMap<String,Map<String,Object>>> serialized) {
		final ItemStack[] itemStackList = new ItemStack[serialized.size()];
		int i = 0;
		for (TreeMap<String,Map<String,Object>> sMap : serialized) {
			try{
				Entry<String, Map<String, Object>> serializedItemStack = sMap.entrySet().iterator().next();
				if (sMap.containsKey("ItemStack")){
					ItemStack is = ItemStack.deserialize(sMap.get("ItemStack"));
					if (sMap.containsKey("ItemMeta") && sMap.get("ItemMeta") != null){
						ItemMeta im = (ItemMeta) ConfigurationSerialization.deserializeObject(sMap.get("ItemMeta"), ConfigurationSerialization.getClassByAlias("ItemMeta"));
						is.setItemMeta(im);
					}
					itemStackList[i++] = is;
				}else{
					itemStackList[i++] = new ItemStack(Material.AIR);
				}
			}catch(Exception ex){
				ex.printStackTrace();
				itemStackList[i++] = new ItemStack(Material.AIR);
			}
		}
		return itemStackList;
	}
	//</editor-fold>
	//<editor-fold defaultstate="collapsed" desc="Base serializers">
	private static List<HashMap<Map<String, Object>, Map<String, Object>>> serializeItemStackList(final ItemStack[] itemStackList) {
		final List<HashMap<Map<String, Object>, Map<String, Object>>> serializedItemStackList = new ArrayList<HashMap<Map<String, Object>, Map<String, Object>>>();
		for (ItemStack itemStack : itemStackList) {
			HashMap<Map<String, Object>, Map<String, Object>> serializedMap = new HashMap<>();
			if (itemStack == null) itemStack = new ItemStack(Material.AIR);
			ItemMeta im = itemStack.hasItemMeta() ? itemStack.getItemMeta() : null;
			Map<String, Object> serializedItemMeta = (im != null) ? im.serialize() : null;
			itemStack.setItemMeta(null);
			Map<String, Object> serializedItemStack = itemStack.serialize();
			itemStack.setItemMeta(im);
			serializedMap.put(serializedItemStack, serializedItemMeta);
			serializedItemStackList.add(serializedMap);
		}
		
		return serializedItemStackList;
	}
		
	private static ItemStack[] deserializeItemStackList(final List<HashMap<Map<String, Object>, Map<String, Object>>> serializedItemStackList) {
		final ItemStack[] itemStackList = new ItemStack[serializedItemStackList.size()];
		
		int i = 0;
		for (HashMap<Map<String, Object>, Map<String, Object>> serializedItemStackMap : serializedItemStackList) {
			Entry<Map<String, Object>, Map<String, Object>> serializedItemStack = serializedItemStackMap.entrySet().iterator().next();
			ItemStack itemStack = ItemStack.deserialize(serializedItemStack.getKey());
			if (serializedItemStack.getValue() != null) {
				ItemMeta itemMeta = (ItemMeta)ConfigurationSerialization.deserializeObject(serializedItemStack.getValue(), ConfigurationSerialization.getClassByAlias("ItemMeta"));
				itemStack.setItemMeta(itemMeta);
			}
			itemStackList[i++] = itemStack;
		}
		
		return itemStackList;
	}
	//</editor-fold>
	//<editor-fold defaultstate="collapsed" desc="Base64">
	/**
	 * Returns empty stack on failure.
	 * @param b64Str
	 * @return 
	 */
	public static ItemStack[] fromBase64Str(final String b64Str){
		if (b64Str == null || "".equals(b64Str.trim())) return new ItemStack[0];
		return fromBase64(b64Str.getBytes());
	}
	
	public static ItemStack[] fromBase64(final byte[] b64){
		try{
			byte[] oBytes = Base64.getDecoder().decode(b64);
			ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(oBytes));
			List<HashMap<Map<String, Object>, Map<String, Object>>> data = (List<HashMap<Map<String, Object>, Map<String, Object>>>) ois.readObject();
			return deserializeItemStackList(data);
		}
		catch (IOException | ClassNotFoundException ex){
			Logger.getLogger(SerializerUtility.class.getName()).log(Level.SEVERE, null, ex);
		}
		return new ItemStack[0];
	}
	/**
	 * Returns null on failure.
	 * @param items
	 * @return 
	 */
	public static String toBase64Str(final ItemStack[] items){
		return new String(toBase64(items));
	}	
	
	/**
	 * Returns null on failure.
	 * @param items
	 * @return 
	 */
	public static byte[] toBase64(final ItemStack[] items){
		List<HashMap<Map<String, Object>, Map<String, Object>>> data = serializeItemStackList(items);
		try{
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(data);
			oos.flush();
			oos.close();
			byte[] oBytes = baos.toByteArray();
			return Base64.getEncoder().encode(oBytes);
		}
		catch (IOException ex){
			Logger.getLogger(SerializerUtility.class.getName()).log(Level.SEVERE, null, ex);
		}
		return new byte[0];
	}	
	//</editor-fold>
	//<editor-fold defaultstate="collapsed" desc="Json">
//	public static  toJson(final ItemStack[] is){
//		try{
//			List<HashMap<Map<String, Object>, Map<String, Object>>> data = serializeItemStackList(is);
//			
//		}
//		catch (IOException | ClassNotFoundException ex){
//			Logger.getLogger(SerializerUtility.class.getName()).log(Level.SEVERE, null, ex);
//		}
//		return new ItemStack[0];
//	}
	//</editor-fold>
}

Comment by md_5 [ 06/Dec/15 ]

JSON? The serializer only supports yaml, via the configuration API.
You are manually calling deserialize and passing an undefined input into the method.

Comment by Taylor [ 06/Dec/15 ]

You're right, it does. So I'm not sure why the deserialize method tried to deserialize it as a Double... cast to an Integer. Either way, it's a pretty easy fix. =]
Both Double and Integer extend/implement Number. Number has a method for getting the int value. So just cast to Number, then use the toIntValue() method.

[{"ItemMeta":null,"ItemStack":{"type":"DISPENSER","amount":10}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}},{"ItemMeta":null,"ItemStack":{"type":"AIR"}}]
Comment by md_5 [ 06/Dec/15 ]

Show me how you are getting an ItemStack with a Double value as it's amount.

Comment by Taylor [ 06/Dec/15 ]

This can cause data loss unless fixed.

Comment by md_5 [ 05/Dec/15 ]

Post your serialized file, the serializer always stores amount as an integer.

Generated at Sat Dec 13 15:18:50 UTC 2025 using Jira 10.3.13#10030013-sha1:56dd970ae30ebfeda3a697d25be1f6388b68a422.