json-lib, com.fasterxml.jackson 둘의 차이점을 비교해본다.

별다른 옵션 없는 심플한 상황이다.

json-lib의 경우 Object형의 NULL 변환에 문제가 있어보인다.


[JsonTest .java]

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

public class JsonTest {
	@Test
	public void test_jsonlib_BeanCase() {
		try {
			TestBean inBean = new TestBean();
			inBean.setA("a Value");
			inBean.setB("");
			inBean.setC(null);
			//inBean.setD();
			
			inBean.setInt11(1);
			//inBean.setInt12();
			inBean.setInt21(1);
			inBean.setInt21(null);
			
			inBean.setBool11(true);
			//inBean.setBool12();
			inBean.setBool21(true);
			inBean.setBool21(null);
			
			JSONObject jsonObj = JSONObject.fromObject( JSONSerializer.toJSON(inBean) );
			String jsonStr = jsonObj.toString();
			System.out.println("jsonStr: " + jsonStr);
			
			jsonObj = (JSONObject) JSONObject.fromObject(jsonStr);
			TestBean outBean = (TestBean) JSONObject.toBean(jsonObj, TestBean.class);
			System.out.println(outBean);
			
			assertEquals(inBean.getA(), outBean.getA());
			assertEquals(inBean.getB(), outBean.getB());
			//assertEquals(inBean.getC(), outBean.getC());				// 문제 소지
			//assertEquals(inBean.getD(), outBean.getD());				// 문제 소지
			
			assertEquals(outBean.getC(), "");
			assertEquals(outBean.getD(), "");
			
			assertEquals(inBean.getInt11(), outBean.getInt11());
			assertEquals(inBean.getInt12(), outBean.getInt12());
			//assertEquals(inBean.getInt21(), outBean.getInt21());		// 문제 소지(아예 변환 안됨)
			//assertEquals(inBean.getInt22(), outBean.getInt22());		// 문제 소지(아예 변환 안됨)
			assertEquals(outBean.getInt21(), new Integer(0));
			assertEquals(outBean.getInt22(), new Integer(0));
			
			assertEquals(inBean.isBool11(), outBean.isBool11());
			assertEquals(inBean.isBool12(), outBean.isBool12());
			//assertEquals(inBean.getBool21(), outBean.getBool21());	// 문제 소지(아예 변환 안됨)
			//assertEquals(inBean.getBool22(), outBean.getBool22());	// 문제 소지(아예 변환 안됨)
			assertEquals(outBean.getBool21(), new Boolean(false));
			assertEquals(outBean.getBool22(), new Boolean(false));
		} catch (Exception e) {
			System.err.println("-- e: " + e);
		}
		
	}
	
	@Test
	public void test_jackson_BeanCase() {
		try {
			ObjectMapper jacksonMapper = new ObjectMapper();
			
			TestBean inBean = new TestBean();
			inBean.setA("a Value");
			inBean.setB("");
			inBean.setC(null);
			//inBean.setD();
			
			inBean.setInt11(1);
			//inBean.setInt12();
			inBean.setInt21(1);
			inBean.setInt21(null);
			
			inBean.setBool11(true);
			//inBean.setBool12();
			inBean.setBool21(true);
			inBean.setBool21(null);
			System.out.println(inBean);
			
			String jsonStr = jacksonMapper.writeValueAsString(inBean);
			System.out.println("jsonStr: " + jsonStr);
			TestBean outBean = jacksonMapper.readValue(jsonStr, TestBean.class);
			
			assertEquals(inBean.getA(), outBean.getA());
			assertEquals(inBean.getB(), outBean.getB());
			assertEquals(inBean.getC(), outBean.getC());
			assertEquals(inBean.getD(), outBean.getD());
			assertEquals(outBean.getC(), null);
			assertEquals(outBean.getD(), null);
			
			assertEquals(inBean.getInt11(), outBean.getInt11());
			assertEquals(inBean.getInt12(), outBean.getInt12());
			assertEquals(inBean.getInt21(), outBean.getInt21());
			assertEquals(inBean.getInt22(), outBean.getInt22());
			
			assertEquals(inBean.isBool11(), outBean.isBool11());
			assertEquals(inBean.isBool12(), outBean.isBool12());
			assertEquals(inBean.getBool21(), outBean.getBool21());
			assertEquals(inBean.getBool22(), outBean.getBool22());
		} catch (IOException e) {
			System.err.println("-- e: " + e);
		}
	}
}

[TestBean.java]

import lombok.Getter;
import lombok.Setter;

@Setter @Getter
public class TestBean {
	String a;
	String b;
	String c;
	String d;
	
	int int11;
	int int12;
	Integer int21;
	Integer int22;
	
	boolean bool11;
	boolean bool12;
	Boolean bool21;
	Boolean bool22;
}

[build.gradle]

sourceCompatibility='1.7'

dependencies {
    //compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    //compile 'org.apache.maven.plugins:maven-war-plugin:+'
    
    compile 'net.sf.json-lib:json-lib:2.4:jdk15@jar'
    compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.6.4'
    
    compile 'commons-lang:commons-lang:2.6'
    compile 'commons-logging:commons-logging:1.2'
    compile 'commons-collections:commons-collections:3.2.2'
    compile 'commons-beanutils:commons-beanutils:1.9.2'
    compile 'net.sf.ezmorph:ezmorph:1.0.6'
    
    testCompile 'junit:junit:4.11'
	testCompile 'org.hamcrest:hamcrest-all:1.3'
}


최근 fastjson 이란 알리바바에서 공개한 JSON Parser 을 보게되어 현재 회사에서 사용중인 Library 들을 비교해 보았다.


jackson-databind-2.1.3.jar

json-lib-2.2.1-jdk15.jar

fastjson-1.2.5.jar


[테스트 상황]

Vo 안에 2개의 Vo가 있는 객체 활용하여, to JSON String, to JAVA Object 로 변환 및 역변환을 10,000 실행하였다.




결과는 위에 보여지는 바와 같습니다.
  - to JSON String = fastjson > jackson > json-lib

  - to Object = jackson > fastjson > json-lib

변환 및 역변환 수치 상으로는 알리바바의 fastjson와 jackson 두가지가 근소한 차이의 성능을 보였습니다.
하지만 알리바바의 fastjson의 경우 유효하지 않는 데이터의 경우 표현자체를 하지 않으므로 인해서 JSON String 길이가 작습니다.
따라서 네트워크 전송 비용까지 감안 한다면 알리바바의 fastjson 의 가장 우수 한것으로 판단됩니다.

※ JSON Str Size가 다른 것은 각 Libaray 마다 null 데이터 처리 규칙이 조금 달라서 그렇고, 유효한 데이터에서는 동일합니다.
    - jackjon = null -> 문자열 null 로 표시
    - json-lib = null -> 문자열 공백으로 표시
    - fastjson = null -> key/value 모두 표시하지 않음



ViewResolver 중에 ContentNegotiatingViewResolver 에대한 메모를 한다.

아마 활용도가 가장 많을듯 싶다.


기존에 "Spring Security(스프링 시큐리티) Exception(인증, 허가)에 따른 공통 처리" 에서 살짝 소스로만 남겨놨었는데 그때는 호출 경로(확장자)에 따른 View선택 방식이였다.

(http://localhost/test.do HTML 데이터 리턴, http://localhost/test.json JSON 데이터 리턴, http://localhost/test.xml XML 데이터 리턴)


그런데 위 Security 셈플링을 하였을때는 더 세부적으로 하지 않아서 몰랐었는데 인증 및 권한획득 과정에서 문제가 발생하였다.


예를 들어 Security를 이용하여 로그인을 시도 하였을때, 인증 실패등의 Exception 상황에 맞는(html,json,xml)을 정보를 리턴해주어야 하는데 Security에서 제공하는 "http://..../j_spring_security_check" 에서는 json, xml 확장자를 붙일수 없다는것이었다.

form-login 의 login-processing-url 속성으로 변경은 가능하지만 현재 do, json, xml 등 1개 이상 등록이 설정파일에서는 못 하는것 같았다.(나만 모를수도...)


그래서 확장자를 통한 방법이 아닌 mediaType 따른 분기 방법을 셈플링 해보았다.

인터넷 상에 MediaTypes 를 ContentNegotiationManagerFactoryBean 에 바로 setter 하는 셈플들이 많을텐데, 3.2 버전인가 어디에서 충돌 버그인가 머시기인가 문제가 있어서 최근 버전에서는 setMediaTypes가 @Deprecated 되었다고 한다.

만약 문제 발생시 디컴파일러를 통해서 해당 함수의 @Deprecated 여부를 확인해보세요!!


MediaType 방식을 사용할경우 Request Header 정보에 따라서 뷰를 결정하게 된다.

Accept 값이 = application/json ....

application/xml ....

= text/html ... 등등등


이러한 값들은 jQuery를 사용할 경우 별도 셋팅이 필요 없이, jQuery.ajax({dataType: "json"....)  값에 따라 자동 셋팅됩니다.



[servlet-context.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
    <mvc:annotation-driven />
    <context:component-scan base-package="pe.kr.ddakker" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>
 
    <mvc:interceptors>
        <bean class="pe.kr.ddakker.dakmoney.web.servlet.interceptor.TestInterceptor"/>
    </mvc:interceptors>
 
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    	<property name="order" value="1" />
	    <property name="contentNegotiationManager">
	        <bean class="org.springframework.web.accept.ContentNegotiationManager">
	        	<constructor-arg>
		            <bean class="org.springframework.web.accept.HeaderContentNegotiationStrategy">
		            </bean>
	            </constructor-arg>
	        </bean>
	    </property>
	    <property name="defaultViews">
	        <list>
	            <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
	        </list>
	    </property>
	</bean>

	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	<property name="order" value="2" />
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

spring 3.2.9 에서 테스트


 menuCd

 highMenuCd

 menuNm

 orderNo

 

 1001

 0

 고객

 1

 1002

 1001

 고객리스트

 2
 1003 1002 대기리스트 3
 1004 0 게시판 4
 1005 1004 자유게시판 5
 1006 1004 유머게시판 6


위와 같은 형식의 데이터를 DB에서 뽑아 냈을때 오른쪽과 같이 화면에 표현하고 싶었다.


우선 Tree는 jQuery기반의 드래그앤랍이 기능이 되는 "jquery.dynatree.js" 를 선택했다.


이제 위 2차원 배열형태의 데이터를 dynatree가 요구하는 데이터형태로 변환을 해야 한다.

요구하는 데이터형태는 트리형식으로 아래와 같은 스타일로 만들어주면 된다.


[

{"isFolder":"true","title":"고객","children":[

{"isFolder":"true","title":"고객리스트" }

...

    ]

....

]


위와 같은 형태로 만들려고 삽질 좀 하다가 javascript 기반으로 잘 만들어져있는 소스를 구글링을통해 발견하여 그것을 java기반으로 수정 하였습니다.

(http://programmingsummaries.tistory.com/250)






import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * JSON관련 가공에 필요한 함수들
 * @auther ddakker 2013. 12. 12.
 */
public class JsonUtil {
	private static final Log log = LogFactory.getLog(JsonUtil.class);
	/**
	 * 2차원 배열의 부모/자식 관계의 데이터를 트리형식으로 재나열 한다.
	 * @param list			2차원 배열
	 * @param rootId		최상위 id
	 * @param idKey			유니크한 키(id가 될 필드명)
	 * @param pIdKey		부모키(pId가 될 필드명)
	 * @param titleKey		메뉴명이 표시될 필드명
	 * @return
	 * @auther ddakker 2013. 12. 12.
	 */
	public static List<Map<String, Object>> convertorTreeMap(final List<Map<String, Object>> list, String rootId, final String idKey, final String pIdKey, final String titleKey){
		return convertorTreeMap(list, rootId, idKey, pIdKey, titleKey, null);
	}
	/**
	 * 2차원 배열의 부모/자식 관계의 데이터를 트리형식으로 재나열 한다.
	 * @param list			2차원 배열
	 * @param rootId		최상위 id
	 * @param idKey			유니크한 키(id가 될 필드명)
	 * @param pIdKey		부모키(pId가 될 필드명)
	 * @param titleKey		메뉴명이 표시될 필드명
	 * @param orderKey		정렬이 필요한경우 정령 필드명
	 * @return
	 * @auther ddakker 2013. 12. 12.
	 */
	public static List<Map<String, Object>> convertorTreeMap(List inList, String rootId, final String idKey, final String pIdKey, final String titleKey, final String orderKey){
		List<Map<String, Object>> treeList = new ArrayList<Map<String,Object>>();	// 최종 트리
		
		if( inList == null || inList.size() == 0 ) 	throw new RuntimeException("List<Map> 데이터가 없습니다.");
		if( inList.get(0) == null ) 				throw new RuntimeException("Map 데이터가 없습니다.");
		
		final List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();	// 원본데이터(Bean일경우 Map으로 변환)
		Iterator iter;
		for( iter=inList.iterator(); iter.hasNext(); ) {
			try{
				Object obj = iter.next();
				if( obj instanceof Map ) {
					list.add((Map<String, Object>) obj);
				}else{
					list.add((Map<String, Object>) BeanUtils.describe(obj));
				}
			}catch (Exception e) {
				throw new RuntimeException("Collection -> List<Map> 으로 변환 중 실패: " + e);
			}
		}
		
		
		int listLength = list.size();
		int loopLength = 0;
		final int[] treeLength = new int[] { 0 };
		
		while ( treeLength[0] != listLength && listLength != loopLength++ ) {
			for ( int i=0; i<list.size(); i++ ) {
				Map<String, Object> item = list.get(i);
				if ( rootId.equals((String)item.get(pIdKey)) ) {
					Map<String, Object> view = new HashMap<String, Object>(item);
					view.put("title", item.get(titleKey));
					view.put("children", new ArrayList<Map<String,Object>>());
					
					treeList.add(view);
					list.remove(i);
					
					treeLength[0]++;
					
					
					if( orderKey != null ){
						Collections.sort(treeList, new Comparator<Map<String, Object>>(){
							public int compare(Map<String, Object> arg0, Map<String, Object> arg1) {
								// TODO Auto-generated method stub
								return ((String)arg0.get(orderKey)).compareTo((String)arg1.get(orderKey));
							}
						});
					}
					view.put("isFolder", "true");
					
					break;
				}else{
					new InnerClass(){
			            public void getParentNode(List<Map<String, Object>> children, Map<String, Object> item ) {
			            	for ( int i=0; i<children.size(); i++ ) {
			    				Map<String, Object> child = children.get(i);
			    				if ( child.get(idKey).equals(item.get(pIdKey)) ) {
			    					Map<String, Object> view = new HashMap<String, Object>(item);
			    					view.put("title", item.get(titleKey));
			    					view.put("children", new ArrayList<Map<String,Object>>());
			    					((List<Map<String,Object>>) child.get("children")).add(view);
			    					
			    					treeLength[0]++;
			    					
			    					list.remove(list.indexOf(item));
			    					view.put("isFolder", "true");
			    					
			    					if( orderKey != null ){
				    					Collections.sort(((List<Map<String,Object>>) child.get("children")), new Comparator<Map<String, Object>>(){
				    						public int compare(Map<String, Object> arg0, Map<String, Object> arg1) {
				    							// TODO Auto-generated method stub
				    							return ((String)arg0.get(orderKey)).compareTo((String)arg1.get(orderKey));
				    						}
				    					});
			    					}
			    					break;
			    				}else{
			    					if( ((List<Map<String,Object>>) child.get("children")).size() > 0 ){
			    						getParentNode((List<Map<String,Object>>) child.get("children"), item);
			    					}
			    				}
			            	}
			            }
			        }.getParentNode(treeList, item);
				}
			}
		}
		return treeList;
	}
	
	public interface InnerClass {
		public void getParentNode(List<Map<String, Object>> list, Map<String, Object> item );
    }
	
}
  • 기본 Bean
      heaer, resultList 와 맵핑될 Bean Class가 업무별로 달라지므로 우선 HashMap에 담고, getter 에서 특정 Bean으로 컨버팅해준다.
  • 
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.commons.beanutils.BeanUtils;
    
    /**
     * DB 아니고 외부 인터페이스에서 받아온 정보
     * @auther ddakker 2013. 6. 12.
     */
    public class ExternalBean {
    
    	private String resultCode;
    	private String errMsg;
    
    	private Map header;
    	private List<Map> resultList;
    	public String getResultCode() {
    		return resultCode;
    	}
    	public void setResultCode(String resultCode) {
    		this.resultCode = resultCode;
    	}
    	public String getErrMsg() {
    		return errMsg;
    	}
    	public void setErrMsg(String errMsg) {
    		this.errMsg = errMsg;
    	}
    	public Map getHeader() {
    		return header;
    	}
    	public void setHeader(Map header) {
    		this.header = header;
    	}
    	public List<Map> getResultList() {
    		return resultList;
    	}
    	public void setResultList(List<Map> resultList) {
    		this.resultList = resultList;
    	}
    
    	/**
    	 * 헤더 정보를 특정 Bean에 담아 리턴한다.
    	 * @param clz
    	 * @return
    	 * @auther ddakker 2013. 6. 14.
    	 */
    	public <T> T getHeader(Class<T> clz) {
    		Object objBean = null;
    		try{
    			objBean = clz.newInstance();
    			BeanUtils.copyProperties(objBean, header);
    
    			return (T) objBean;
    		}catch(Exception e){
    			throw ExceptionManager.createException("JSON 데이터를 Bean으로 맵핑[Header]하는 도중 에러가 발생하였습니다." + e);
    		}
    	}
    
    	/**
    	 * 배열 정보를 특정 List<Bean>에 담아 리턴한다.
    	 * @param clz
    	 * @return
    	 * @auther ddakker 2013. 6. 14.
    	 */
    	public <T> List<T> getResultList(Class<T> clz) {
    		List<T> list = new ArrayList<T>();
    		try{
    			Object objBean;
    			for( Map map : resultList ){
    				objBean = clz.newInstance();
    				BeanUtils.copyProperties(objBean, map);
    
    				list.add((T) objBean);
    			}
    		}catch(Exception e){
    			throw ExceptionManager.createException("JSON 데이터를 Bean으로 맵핑[ResultList]하는 도중 에러가 발생하였습니다." + e);
    		}
    		return list;
    	}
    }
    

  • 헤더 Bean
      각 업무에 맞는 Bean을 생성한다.
  • public class UserHeaderBean {
    	private String userNm;
    
    	public String getUserNm() {
    		return userNm;
    	}
    
    	public void setUserNm(String userNm) {
    		this.userNm = userNm;
    	}
    }
    

  • 배열 Bean
      각 업무에 맞는 Bean을 생성한다.
  • public class ListBean {
    	private String dlvrAddr1;
    	
    	public String getDlvrAddr1() {
    		return dlvrAddr1;
    	}
    }
    

    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import net.sf.json.JSONObject;
    
    public class TestCase {
            @Test
    	public void jsonTestCase() {
    		String jsonStr = "{'resultCode':'success', 'errorMsg': '','header':{'userNm':'ddakker'},'resultList':[{'dlvrAddr1': '서울~'},{'dlvrAddr1': '충남~'}]}";
    		
    		Map classMap = new HashMap();
    		classMap.put("header", HashMap.class);
    		classMap.put("resultList", HashMap.class);
    
    		JSONObject json = JSONObject.fromObject(jsonStr);
    
    		ExternalBean exBean = (ExternalBean) JSONObject.toBean(json, ExternalBean.class, classMap);
    		
    		if( exBean.getResultCode().equals("success") ){
    			UserHeaderBean hBean = exBean.getHeader(UserHeaderBean.class);
    			System.out.println(hBean.geUserNm());
    								
    			List<ListBean> lBeanList = exBean.getResultList(ListBean.class);
    			System.out.println(lBeanList.size());
    			System.out.println(lBeanList.get(0).getDlvrAddr1());
    		}
    	}
    }
    
    • json-lib-2.4-jdk15.jar
    • ezmorph-1.0.6.jar
    • commons-lang-2.6.jar
    • commons-logging-1.1.3.jar
    • commons-collections-3.2.1.jar
    • commons-beanutils-1.8.3.jar
    // Map -> JSON
    JSONObject jsonMap = JSONObject.fromObject(map);
    
    // List -> Map -> JSON
    JSONArray listJson = JSONArray.fromObject(list);
    
    // XML String -> JSON
    String xml = "
    ddakker100
    ab
    "; JSONObject json = (JSONObject) new XMLSerializer().read(xml); System.out.println(json); JSONObject header = (JSONObject) json.get("header"); System.out.println("header: " + header); System.out.println(((JSONObject)json.get("header")).get("name")); JSONArray list = (JSONArray) json.get("list"); System.out.println("list: " + list); System.out.println("list: " + list.size()); for( int i=0; i<list.size(); i++ ){ System.out.println("list[" + i + "]: " + list.getJSONObject(i).get("friend")); }

    + Recent posts