json 里的value 如何从不同的上下文中取值

158 阅读2分钟

如题所述

需求

比如想获取这样的一个json结构

{
    "user_id":"user.id",//获取用户ID
    "book_id":"book.id",//获取书籍ID,
}

很简单可以实现

var user = {
    id: 1
}

var book = {
    id: 2
}


function getJson(user_id, book_id){
    return {
        "user_id": user_id,
        "book_id": book_id
    }
}
console.log(getJson(user.id,book.id))

这样简单的结构这样来实现,没啥问题,也很直观。

假如想获取

{
    "user_id":"user.id",//获取用户ID
    "book_id":"book.id",//获取书籍ID,
    "adress_id":"address.id"// 获取地址ID
}

代码要改下

var user = {
    id: 1
}

var book = {
    id: 2
}

var address = {
    id: 3
}


function getJson(user_id, book_id, address_id){
    return {
        "user_id": user_id,
        "book_id": book_id,
        "address_id": address_id
    }
}

console.log(getJson(user.id,book.id,address.id))

随着获取结构越来越复杂,代码会越来越臃肿。

如何简化

json 结构 的value 值,是从不同实体获取到的,那么这个value值,可以把它定义成一个结构,来表述value。

比如这样(潜套对象)

{
    "user_id":":user.id",
    "book_id":":book.id",
    "address_id": {
        "_data_source": "address",
        "id": ":id"
    }
}

还有这样(数组)

[    ":user.id",    ":book.id",    ":address.id"]

更简单的像这样

":user.id"

以冒号开头的说明是要从实体中动态获取的。

需要定义一个解析器,对上方的结构进行解析

解析器定义为


var test = {
    is_array: function(value) {
        if (typeof Array.isArray === 'function') {
            return Array.isArray(value)
        }
        return Object.prototype.toString.call(value) === '[object Array]'
    },
    is_object: function (value) {
        return Object.prototype.toString.call(value) === '[object Object]'
    },
    is_string: function(value) {
        return typeof value === 'string'
    }
}

function replaceParams(params, current_data_source) {
    if (test.is_array(params)) {
        params = params.map(param=>replaceParams(param, current_data_source))
    }
    else if (test.is_object(params)) {
        if (params._data_source) {
            params = replaceParams(params.key, getDataSource(params))
        } else {
            if (test.is_array(current_data_source)) {
                params = current_data_source.map(item=> replaceParams(JSON.parse(JSON.stringify(params)), item))
            } else {
                Object.keys(params).map(key => params[key]=replaceParams(params[key], current_data_source))
            }
            
        }
    }
    else if (test.is_string(params)) {
        params = replaceParam(params, current_data_source)
    }
    
    return params
}

function replaceParam(param, obj) {
    
    if (!test.is_string(param)) {
        return param
    }
    
    if (param.indexOf(':') < 0) {
		return param;
	}
	
	if (param.startsWith(':')) {
		console.log('replaceParam', param, '->', data_get(obj, param.slice(1)))
		return data_get(obj, param.slice(1));
	}
	return param.replace(/:([\w\.]+)/g, function (match, key) {
		console.log('replaceParam', ':'+key, '->', data_get(obj, key))
		return data_get(obj, key);
	});
}

还需要定义一个实体容器,里面有user、book、address, 简称为数据源

数据源定义为

var user = {
    id: 1
}

var book = {
    id: 2
}

var address = {
    id: 3
}
var data_sources = {}

data_sources.user = user
data_sources.book = book
data_sources.address = address

function getDataSource(config) {
    return data_sources[config._data_source] || data_sources
}

默认返回 data_sources

需要实现一个 data_get 方法

类似于这样 data_get(obj, 'user.id')


function data_get(target, path, fallback) {
	let segments = Array.isArray(path) ? path : path.split('.');
	let [segment] = segments;

	let find = target;

	if (segment !== '*' && segments.length > 0) {
		if (find[segment] === null || typeof find[segment] === 'undefined') {
			find = typeof fallback === 'function' ? fallback() : fallback;
		}
		else {
			find = data_get(find[segment], segments.slice(1), fallback);
		}
	}

	else if (segment === '*') {
		const partial = segments.slice(path.indexOf('*') + 1, path.length);

		if (typeof find === 'object') {
			find = Object.keys(find).reduce((build, property) => ({
				...build,
				[property]: data_get(find[property], partial, fallback)
			}),
				{});
		}
		else {
			find = data_get(find, partial, fallback);
		}
	}


	/*-----------------------------------------------------------------------------
	 |   Arrayable Requirements
	 *-----------------------------------------------------------------------------
	 |
	 |   . All arrays are converted to objects
	 |   . For Example
	 |      #Code
	 |        Code -> data_set({ list: ['one', 'two', 'three'], 'list.*', 'update', true });
	 |
	 |      #Input
	 |         Input -> { list: ['one', 'two', 'three'] }
	 |
	 |      #During We Convert Arrays To "Indexed Objects"
	 |         During -> { list: { '1': 'one', '2': 'two', '3': 'three' } }
	 |
	 |      #Before Output we convert "Indexed Objects" Back To Arrays
	 |         From -> { list: { '1': 'update', '2': 'update', '3': 'update' } }
	 |         Into -> { list: ['update', 'update', 'update'] }
	 |
	 |   . Arrays convert into "Indexed Objects", allowing for wildcard (*) capabilities
	 |   . "Indexed Objects" are converted back into arrays before returning the updated target
	 |
	 */
	if (typeof find === 'object') {
		if (Object.prototype.toString.call(find) === '[object Array]') {
			if (find.length == 0) {
				return find
			}
		}
		if (Object.keys(find).length > 0) {
			const isArrayTransformable = Object.keys(find).every(index => index.match(/^(0|[1-9][0-9]*)$/));

			return isArrayTransformable ? Object.values(find) : find;
		}
	} else {
		return find;
	}
};

这样就定义完成了,所有代码为


var user = {
    id: 1
}

var book = {
    id: 2
}

var address = {
    id: 3
}
var data_sources = {}

data_sources.user = user
data_sources.book = book
data_sources.address = address

function getDataSource(config) {
    return data_sources[config._data_source] || data_sources
}


var test = {
    is_array: function(value) {
        if (typeof Array.isArray === 'function') {
            return Array.isArray(value)
        }
        return Object.prototype.toString.call(value) === '[object Array]'
    },
    is_object: function (value) {
        return Object.prototype.toString.call(value) === '[object Object]'
    },
    is_string: function(value) {
        return typeof value === 'string'
    }
}

function replaceParams(params, current_data_source) {
    if (test.is_array(params)) {
        params = params.map(param=>replaceParams(param, current_data_source))
    }
    else if (test.is_object(params)) {
        if (params._data_source) {
            params = replaceParams(params.key, getDataSource(params))
        } else {
            if (test.is_array(current_data_source)) {
                params = current_data_source.map(item=> replaceParams(JSON.parse(JSON.stringify(params)), item))
            } else {
                Object.keys(params).map(key => params[key]=replaceParams(params[key], current_data_source))
            }
            
        }
    }
    else if (test.is_string(params)) {
        params = replaceParam(params, current_data_source)
    }
    
    return params
}

function replaceParam(param, obj) {
    
    if (!test.is_string(param)) {
        return param
    }
    
    if (param.indexOf(':') < 0) {
		return param;
	}
	
	if (param.startsWith(':')) {
		console.log('replaceParam', param, '->', data_get(obj, param.slice(1)))
		return data_get(obj, param.slice(1));
	}
	return param.replace(/:([\w\.]+)/g, function (match, key) {
		console.log('replaceParam', ':'+key, '->', data_get(obj, key))
		return data_get(obj, key);
	});
}

function data_get(target, path, fallback) {
	let segments = Array.isArray(path) ? path : path.split('.');
	let [segment] = segments;

	let find = target;

	if (segment !== '*' && segments.length > 0) {
		if (find[segment] === null || typeof find[segment] === 'undefined') {
			find = typeof fallback === 'function' ? fallback() : fallback;
		}
		else {
			find = data_get(find[segment], segments.slice(1), fallback);
		}
	}

	else if (segment === '*') {
		const partial = segments.slice(path.indexOf('*') + 1, path.length);

		if (typeof find === 'object') {
			find = Object.keys(find).reduce((build, property) => ({
				...build,
				[property]: data_get(find[property], partial, fallback)
			}),
				{});
		}
		else {
			find = data_get(find, partial, fallback);
		}
	}


	/*-----------------------------------------------------------------------------
	 |   Arrayable Requirements
	 *-----------------------------------------------------------------------------
	 |
	 |   . All arrays are converted to objects
	 |   . For Example
	 |      #Code
	 |        Code -> data_set({ list: ['one', 'two', 'three'], 'list.*', 'update', true });
	 |
	 |      #Input
	 |         Input -> { list: ['one', 'two', 'three'] }
	 |
	 |      #During We Convert Arrays To "Indexed Objects"
	 |         During -> { list: { '1': 'one', '2': 'two', '3': 'three' } }
	 |
	 |      #Before Output we convert "Indexed Objects" Back To Arrays
	 |         From -> { list: { '1': 'update', '2': 'update', '3': 'update' } }
	 |         Into -> { list: ['update', 'update', 'update'] }
	 |
	 |   . Arrays convert into "Indexed Objects", allowing for wildcard (*) capabilities
	 |   . "Indexed Objects" are converted back into arrays before returning the updated target
	 |
	 */
	if (typeof find === 'object') {
		if (Object.prototype.toString.call(find) === '[object Array]') {
			if (find.length == 0) {
				return find
			}
		}
		if (Object.keys(find).length > 0) {
			const isArrayTransformable = Object.keys(find).every(index => index.match(/^(0|[1-9][0-9]*)$/));

			return isArrayTransformable ? Object.values(find) : find;
		}
	} else {
		return find;
	}
};


验证

console.log(replaceParams({
    "user_id":":user.id",
    "book_id":":book.id",
    "address_id": {
        "_data_source": "address",
        "key": ":id"
    }
}, data_sources))
//output  {user_id:1,book_id:2,address_id:3}

console.log(replaceParams([
    ":user.id",
    ":book.id",
    ":address.id"
], data_sources))
//output [1,2, 3]

console.log(replaceParams([
    ":user.id",
    ":book.id",
    {
        "_data_source": "address",
        "key": ":id"
    }
], data_sources))
//output [1, 2, 3]


console.log(replaceParams([
    ":user.id",
    ":book.id",
    [
        ":user.id",
        ":book.id",
    ]
], data_sources))
//output [1, 2, [1,2]]


console.log(replaceParams([
    ":user.id",
    ":book.id",
    [
        ":user.id",
        ":book.id",
        ":address"
    ]
], data_sources))


//output [1, 2, [1,2,{id:3}]]

高级用法

data_sources.users = [
    {
        id:1,
        name:"11"
    },
    {
        id:2,
        name:"22"
    }
]
data_sources.post = {
    id:4,
    comment:{
        id:5
    }
}

console.log(JSON.stringify(replaceParams({
    'test': [
        ":user.id",
        ":book.id",
        [
            ":user.id",
            ":book.id",
            ":address"
        ]
    ],
    "users": ":users",
    "_map_users": {
        "_data_source":"users",
        "key": {
            "name": ":name"
        }
    },
    "user_ids": ":users.*.id",
    "extra": {
        "user_ids": ":users.*.id",
        "_user_ids": {
            "_data_source":"users",
            "key":":*.id"
        },
        "post": {
            "_data_source":"post",
            "key": {
                "post_id": ":id",
                "comment": ":comment",
                "comment_id":":comment.id",
                "_user_ids": {
                    "_data_source":"users",
                    "key":":*.id"
                }
            }
        }
    }
}, data_sources)))

// output {"test":[1,2,[1,2,{"id":3}]],"users":[{"id":1,"name":"11"},{"id":2,"name":"22"}],"_map_users":[{"name":"11"},{"name":"22"}],"user_ids":[1,2],"extra":{"user_ids":[1,2],"_user_ids":[1,2],"post":{"post_id":4,"comment":{"id":5},"comment_id":5,"_user_ids":[1,2]}}}

更进一步

  • 当数据源是 callback 时

  • 当callback 返回的是promise 时

  • 当这个思路应用到laravel的项目中时,比如api返回的结构

ref