mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add BeanPath
This commit is contained in:
parent
b9274f5d6e
commit
ed1c3e48af
@ -1,317 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* https://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.bean;
|
||||
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.collection.CollUtil;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.convert.Convert;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.math.NumberUtil;
|
||||
import org.dromara.hutool.core.text.CharUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.text.split.SplitUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br>
|
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种:
|
||||
* <ol>
|
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li>
|
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* 表达式栗子:
|
||||
*
|
||||
* <pre>
|
||||
* persion
|
||||
* persion.name
|
||||
* persons[3]
|
||||
* person.friends[5].name
|
||||
* ['person']['friends'][5]['name']
|
||||
* </pre>
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.0.6
|
||||
*/
|
||||
public class BeanPathOld implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 表达式边界符号数组
|
||||
*/
|
||||
private static final char[] EXP_CHARS = {CharUtil.DOT, CharUtil.BRACKET_START, CharUtil.BRACKET_END};
|
||||
|
||||
private boolean isStartWith = false;
|
||||
protected List<String> patternParts;
|
||||
|
||||
/**
|
||||
* 解析Bean路径表达式为Bean模式<br>
|
||||
* Bean表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br>
|
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种:
|
||||
* <ol>
|
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li>
|
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* 表达式栗子:
|
||||
*
|
||||
* <pre>
|
||||
* persion
|
||||
* persion.name
|
||||
* persons[3]
|
||||
* person.friends[5].name
|
||||
* ['person']['friends'][5]['name']
|
||||
* </pre>
|
||||
*
|
||||
* @param expression 表达式
|
||||
* @return BeanPath
|
||||
*/
|
||||
public static BeanPathOld of(final String expression) {
|
||||
return new BeanPathOld(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param expression 表达式
|
||||
*/
|
||||
public BeanPathOld(final String expression) {
|
||||
init(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表达式解析后的分段列表
|
||||
*
|
||||
* @return 表达式分段列表
|
||||
*/
|
||||
public List<String> getPatternParts() {
|
||||
return this.patternParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean中对应表达式的值
|
||||
*
|
||||
* @param bean Bean对象或Map或List等
|
||||
* @return 值,如果对应值不存在,则返回null
|
||||
*/
|
||||
public Object get(final Object bean) {
|
||||
return get(this.patternParts, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表达式指定位置(或filed对应)的值<br>
|
||||
* 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值<br>
|
||||
* 注意:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
|
||||
* 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
|
||||
* </pre>
|
||||
*
|
||||
* @param bean Bean、Map或List
|
||||
* @param value 值
|
||||
*/
|
||||
public void set(final Object bean, final Object value) {
|
||||
Objects.requireNonNull(bean);
|
||||
|
||||
Object subBean = bean;
|
||||
Object previousBean = null;
|
||||
boolean isFirst = true;
|
||||
String patternPart;
|
||||
// 尝试找到倒数第二个子对象, 最终需要设置它的字段值
|
||||
final int length = patternParts.size() - 1;
|
||||
|
||||
// 填充父字段缺失的对象
|
||||
for (int i = 0; i < length; i++) {
|
||||
patternPart = patternParts.get(i);
|
||||
// 保存当前操作的bean, 以便subBean不存在时, 可以用来填充缺失的子对象
|
||||
previousBean = subBean;
|
||||
// 获取当前对象的子对象
|
||||
subBean = getFieldValue(subBean, patternPart);
|
||||
if (null == subBean) {
|
||||
// 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作)
|
||||
if (isFirst && !this.isStartWith && BeanUtil.isMatchName(bean, patternPart, true)) {
|
||||
subBean = bean;
|
||||
isFirst = false;
|
||||
} else {
|
||||
// 填充缺失的子对象, 根据下一个表达式决定填充的值, 如果是整数(下标)则使用列表, 否则当做Map对象
|
||||
subBean = NumberUtil.isInteger(patternParts.get(i + 1)) ? new ArrayList<>() : new HashMap<>();
|
||||
BeanUtil.setFieldValue(previousBean, patternPart, subBean);
|
||||
// 上面setFieldValue中有可能发生对象转换, 因此此处重新获取子对象
|
||||
// 欲知详情请自行阅读FieldUtil.setFieldValue(Object, Field, Object)
|
||||
subBean = BeanUtil.getFieldValue(previousBean, patternPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置最终的(当前)字段值
|
||||
final Object newSubBean = BeanUtil.setFieldValue(subBean, patternParts.get(length), value);
|
||||
if(newSubBean != subBean && null != previousBean){
|
||||
// 对象变更,重新加入
|
||||
BeanUtil.setFieldValue(previousBean, patternParts.get(length - 1), newSubBean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.patternParts.toString();
|
||||
}
|
||||
|
||||
//region Private Methods
|
||||
|
||||
/**
|
||||
* 获取Bean中对应表达式的值
|
||||
*
|
||||
* @param patternParts 表达式分段列表
|
||||
* @param bean Bean对象或Map或List等
|
||||
* @return 值,如果对应值不存在,则返回null
|
||||
*/
|
||||
private Object get(final List<String> patternParts, final Object bean) {
|
||||
Object subBean = bean;
|
||||
boolean isFirst = true;
|
||||
for (final String patternPart : patternParts) {
|
||||
subBean = getFieldValue(subBean, patternPart);
|
||||
if (null == subBean) {
|
||||
// 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作)
|
||||
if (isFirst && !this.isStartWith && BeanUtil.isMatchName(bean, patternPart, true)) {
|
||||
subBean = bean;
|
||||
isFirst = false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return subBean;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Object getFieldValue(final Object bean, final String expression) {
|
||||
if (StrUtil.isBlank(expression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (StrUtil.contains(expression, CharUtil.COLON)) {
|
||||
// [start:end:step] 模式
|
||||
final List<String> parts = SplitUtil.splitTrim(expression, StrUtil.COLON);
|
||||
final int start = Integer.parseInt(parts.get(0));
|
||||
final int end = Integer.parseInt(parts.get(1));
|
||||
int step = 1;
|
||||
if (3 == parts.size()) {
|
||||
step = Integer.parseInt(parts.get(2));
|
||||
}
|
||||
if (bean instanceof Collection) {
|
||||
return CollUtil.sub((Collection<?>) bean, start, end, step);
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
return ArrayUtil.sub(bean, start, end, step);
|
||||
}
|
||||
} else if (StrUtil.contains(expression, ',')) {
|
||||
// [num0,num1,num2...]模式或者['key0','key1']模式
|
||||
final List<String> keys = SplitUtil.splitTrim(expression, StrUtil.COMMA);
|
||||
if (bean instanceof Collection) {
|
||||
return CollUtil.getAny((Collection<?>) bean, Convert.convert(int[].class, keys));
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
return ArrayUtil.getAny(bean, Convert.convert(int[].class, keys));
|
||||
} else {
|
||||
final String[] unWrappedKeys = new String[keys.size()];
|
||||
for (int i = 0; i < unWrappedKeys.length; i++) {
|
||||
unWrappedKeys[i] = StrUtil.unWrap(keys.get(i), CharUtil.SINGLE_QUOTE);
|
||||
}
|
||||
if (bean instanceof Map) {
|
||||
// 只支持String为key的Map
|
||||
return MapUtil.getAny((Map<String, ?>) bean, unWrappedKeys);
|
||||
} else {
|
||||
final Map<String, Object> map = BeanUtil.beanToMap(bean);
|
||||
return MapUtil.getAny(map, unWrappedKeys);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 数字或普通字符串
|
||||
return BeanUtil.getFieldValue(bean, expression);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param expression 表达式
|
||||
*/
|
||||
private void init(final String expression) {
|
||||
final List<String> localPatternParts = new ArrayList<>();
|
||||
final int length = expression.length();
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
char c;
|
||||
boolean isNumStart = false;// 下标标识符开始
|
||||
boolean isInWrap = false; //标识是否在引号内
|
||||
for (int i = 0; i < length; i++) {
|
||||
c = expression.charAt(i);
|
||||
if (0 == i && '$' == c) {
|
||||
// 忽略开头的$符,表示当前对象
|
||||
isStartWith = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('\'' == c) {
|
||||
// 结束
|
||||
isInWrap = (!isInWrap);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isInWrap && ArrayUtil.contains(EXP_CHARS, c)) {
|
||||
// 处理边界符号
|
||||
if (CharUtil.BRACKET_END == c) {
|
||||
// 中括号(数字下标)结束
|
||||
if (!isNumStart) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find ']' but no '[' !", expression, i));
|
||||
}
|
||||
isNumStart = false;
|
||||
// 中括号结束加入下标
|
||||
} else {
|
||||
if (isNumStart) {
|
||||
// 非结束中括号情况下发现起始中括号报错(中括号未关闭)
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, i));
|
||||
} else if (CharUtil.BRACKET_START == c) {
|
||||
// 数字下标开始
|
||||
isNumStart = true;
|
||||
}
|
||||
// 每一个边界符之前的表达式是一个完整的KEY,开始处理KEY
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(builder.toString());
|
||||
}
|
||||
builder.setLength(0);
|
||||
} else {
|
||||
// 非边界符号,追加字符
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 末尾边界符检查
|
||||
if (isNumStart) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, length - 1));
|
||||
} else {
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(builder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// 不可变List
|
||||
this.patternParts = ListUtil.view(localPatternParts);
|
||||
}
|
||||
//endregion
|
||||
}
|
@ -16,6 +16,7 @@ import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.bean.copier.BeanCopier;
|
||||
import org.dromara.hutool.core.bean.copier.CopyOptions;
|
||||
import org.dromara.hutool.core.bean.copier.ValueProvider;
|
||||
import org.dromara.hutool.core.bean.path.BeanPath;
|
||||
import org.dromara.hutool.core.collection.CollUtil;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.collection.set.SetUtil;
|
||||
@ -67,7 +68,7 @@ public class BeanUtil {
|
||||
* @see #hasPublicField(Class)
|
||||
*/
|
||||
public static boolean isReadableBean(final Class<?> clazz) {
|
||||
if(null == clazz){
|
||||
if (null == clazz) {
|
||||
return false;
|
||||
}
|
||||
return hasGetter(clazz) || hasPublicField(clazz);
|
||||
@ -87,7 +88,7 @@ public class BeanUtil {
|
||||
* @see #hasPublicField(Class)
|
||||
*/
|
||||
public static boolean isWritableBean(final Class<?> clazz) {
|
||||
if(null == clazz){
|
||||
if (null == clazz) {
|
||||
return false;
|
||||
}
|
||||
return hasSetter(clazz) || hasPublicField(clazz);
|
||||
@ -244,7 +245,7 @@ public class BeanUtil {
|
||||
private static Map<String, PropertyDescriptor> internalGetPropertyDescriptorMap(final Class<?> clazz, final boolean ignoreCase) throws BeanException {
|
||||
final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz);
|
||||
final Map<String, PropertyDescriptor> map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1f)
|
||||
: new HashMap<>(propertyDescriptors.length, 1);
|
||||
: new HashMap<>(propertyDescriptors.length, 1);
|
||||
|
||||
for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
|
||||
map.put(propertyDescriptor.getName(), propertyDescriptor);
|
||||
@ -355,7 +356,7 @@ public class BeanUtil {
|
||||
* @param bean Bean对象,支持Map、List、Collection、Array
|
||||
* @param expression 表达式,例如:person.friend[5].name
|
||||
* @return Bean属性值,bean为{@code null}或者express为空,返回{@code null}
|
||||
* @see BeanPathOld#get(Object)
|
||||
* @see BeanPath#getValue(Object)
|
||||
* @since 3.0.7
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -363,7 +364,7 @@ public class BeanUtil {
|
||||
if (null == bean || StrUtil.isBlank(expression)) {
|
||||
return null;
|
||||
}
|
||||
return (T) BeanPathOld.of(expression).get(bean);
|
||||
return (T) BeanPath.of(expression).getValue(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -372,11 +373,11 @@ public class BeanUtil {
|
||||
* @param bean Bean对象,支持Map、List、Collection、Array
|
||||
* @param expression 表达式,例如:person.friend[5].name
|
||||
* @param value 属性值
|
||||
* @see BeanPathOld#get(Object)
|
||||
* @see BeanPath#setValue(Object, Object)
|
||||
* @since 4.0.6
|
||||
*/
|
||||
public static void setProperty(final Object bean, final String expression, final Object value) {
|
||||
BeanPathOld.of(expression).set(bean, value);
|
||||
BeanPath.of(expression).setValue(bean, value);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------- mapToBean
|
||||
@ -625,9 +626,9 @@ public class BeanUtil {
|
||||
}
|
||||
|
||||
return BeanCopier.of(bean, targetMap,
|
||||
CopyOptions.of()
|
||||
.setIgnoreNullValue(ignoreNullValue)
|
||||
.setFieldEditor(keyEditor)
|
||||
CopyOptions.of()
|
||||
.setIgnoreNullValue(ignoreNullValue)
|
||||
.setFieldEditor(keyEditor)
|
||||
).copy();
|
||||
}
|
||||
|
||||
@ -673,7 +674,7 @@ public class BeanUtil {
|
||||
if (null == source) {
|
||||
return null;
|
||||
}
|
||||
if(RecordUtil.isRecord(tClass)){
|
||||
if (RecordUtil.isRecord(tClass)) {
|
||||
// issue#I7EO3U
|
||||
// 转换record时,ignoreProperties无效
|
||||
return (T) RecordConverter.INSTANCE.convert(tClass, source);
|
||||
@ -741,7 +742,7 @@ public class BeanUtil {
|
||||
}
|
||||
|
||||
// issue#3091
|
||||
if(ClassUtil.isBasicType(targetType) || String.class == targetType){
|
||||
if (ClassUtil.isBasicType(targetType) || String.class == targetType) {
|
||||
return Convert.toList(targetType, collection);
|
||||
}
|
||||
|
||||
@ -864,7 +865,7 @@ public class BeanUtil {
|
||||
* @since 5.0.7
|
||||
*/
|
||||
public static boolean isNotEmpty(final Object bean, final String... ignoreFieldNames) {
|
||||
if(null == bean){
|
||||
if (null == bean) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -887,7 +888,7 @@ public class BeanUtil {
|
||||
public static boolean hasNullField(final Object bean, final String... ignoreFieldNames) {
|
||||
return checkBean(bean, field ->
|
||||
(!ArrayUtil.contains(ignoreFieldNames, field.getName()))
|
||||
&& null == FieldUtil.getFieldValue(bean, field)
|
||||
&& null == FieldUtil.getFieldValue(bean, field)
|
||||
);
|
||||
}
|
||||
|
||||
@ -913,11 +914,11 @@ public class BeanUtil {
|
||||
* 断言为{@code true} 时,返回{@code true}并不再检查后续字段;<br>
|
||||
* 断言为{@code false}时,继续检查后续字段
|
||||
*
|
||||
* @param bean Bean
|
||||
* @param bean Bean
|
||||
* @param predicate 断言
|
||||
* @return 是否触发断言为真
|
||||
*/
|
||||
public static boolean checkBean(final Object bean, final Predicate<Field> predicate){
|
||||
public static boolean checkBean(final Object bean, final Predicate<Field> predicate) {
|
||||
if (null == bean) {
|
||||
return true;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
package org.dromara.hutool.core.bean;
|
||||
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.collection.CollUtil;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.convert.Convert;
|
||||
@ -37,7 +38,7 @@ public class DynaBean implements Cloneable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Class<?> beanClass;
|
||||
private final Object bean;
|
||||
private Object bean;
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
@ -101,6 +102,13 @@ public class DynaBean implements Cloneable, Serializable {
|
||||
// 非数字,see pr#254@Gitee
|
||||
return (T) CollUtil.map((Collection<?>) bean, (beanEle) -> DynaBean.of(beanEle).get(fieldName), false);
|
||||
}
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
try {
|
||||
return ArrayUtil.get(bean, Integer.parseInt(fieldName));
|
||||
} catch (final NumberFormatException e) {
|
||||
// 非数字,see pr#254@Gitee
|
||||
return (T) ArrayUtil.map(bean, Object.class, (beanEle) -> DynaBean.of(beanEle).get(fieldName));
|
||||
}
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if (null == prop) {
|
||||
@ -148,21 +156,27 @@ public class DynaBean implements Cloneable, Serializable {
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @param value 字段值
|
||||
* @return this;
|
||||
* @throws BeanException 反射获取属性值或字段值导致的异常
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void set(final String fieldName, final Object value) throws BeanException {
|
||||
public DynaBean set(final String fieldName, final Object value) throws BeanException {
|
||||
if (Map.class.isAssignableFrom(beanClass)) {
|
||||
((Map) bean).put(fieldName, value);
|
||||
} else if (bean instanceof List) {
|
||||
ListUtil.setOrPadding((List) bean, Convert.toInt(fieldName), value);
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
// issue#3008,追加产生新数组,此处返回新数组
|
||||
this.bean = ArrayUtil.setOrPadding(bean, Convert.toInt(fieldName), value);
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if (null == prop) {
|
||||
throw new BeanException("No public field or set method for '{}'", fieldName);
|
||||
}
|
||||
prop.setValue(bean, value);
|
||||
|
||||
prop.setValue(bean, value, false, false);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,10 @@ import org.dromara.hutool.core.annotation.AnnotationUtil;
|
||||
import org.dromara.hutool.core.annotation.PropIgnore;
|
||||
import org.dromara.hutool.core.convert.Convert;
|
||||
import org.dromara.hutool.core.func.LambdaUtil;
|
||||
import org.dromara.hutool.core.reflect.*;
|
||||
import org.dromara.hutool.core.reflect.FieldUtil;
|
||||
import org.dromara.hutool.core.reflect.ModifierUtil;
|
||||
import org.dromara.hutool.core.reflect.ReflectUtil;
|
||||
import org.dromara.hutool.core.reflect.TypeUtil;
|
||||
import org.dromara.hutool.core.reflect.method.MethodUtil;
|
||||
|
||||
import java.beans.Transient;
|
||||
|
@ -171,23 +171,29 @@ public class BeanPath implements Iterator<BeanPath> {
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param value 设置的值
|
||||
* @return bean。如果在原Bean对象基础上设置值,返回原Bean,否则返回新的Bean
|
||||
*/
|
||||
public void setValue(Object bean, final Object value) {
|
||||
Object parentBean;
|
||||
BeanPath beanPath = this;
|
||||
while (beanPath.hasNext()) {
|
||||
parentBean = bean;
|
||||
bean = beanPath.node.getValue(bean);
|
||||
if (null == bean) {
|
||||
final BeanPath child = beanPath.next();
|
||||
bean = isListNode(child.node) ? new ArrayList<>() : new HashMap<>();
|
||||
beanPath.node.setValue(parentBean, bean);
|
||||
// 如果自定义put方法修改了value,此处二次get避免丢失
|
||||
bean = beanPath.node.getValue(parentBean);
|
||||
}
|
||||
beanPath = beanPath.next();
|
||||
public Object setValue(final Object bean, final Object value) {
|
||||
if (!hasNext()) {
|
||||
// 根节点,直接赋值
|
||||
return this.node.setValue(bean, value);
|
||||
}
|
||||
beanPath.node.setValue(bean, value);
|
||||
|
||||
final BeanPath childBeanPath = next();
|
||||
Object subBean = this.node.getValue(bean);
|
||||
if (null == subBean) {
|
||||
subBean = isListNode(childBeanPath.node) ? new ArrayList<>() : new HashMap<>();
|
||||
this.node.setValue(bean, subBean);
|
||||
// 如果自定义put方法修改了value,返回修改后的value,避免值丢失
|
||||
subBean = this.node.getValue(bean);
|
||||
}
|
||||
// 递归逐层查找子节点,赋值
|
||||
final Object newSubBean = childBeanPath.setValue(subBean, value);
|
||||
if(newSubBean != subBean){
|
||||
//对于数组对象,set新值后,会返回新的数组,此时将新对象再加入父bean中,覆盖旧数组
|
||||
this.node.setValue(bean, newSubBean);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -30,7 +30,8 @@ public class EmptyNode implements Node {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(final Object bean, final Object value) {
|
||||
public Object setValue(final Object bean, final Object value) {
|
||||
// do nothing
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ public class ListNode implements Node{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(final Object bean, final Object value) {
|
||||
public Object setValue(final Object bean, final Object value) {
|
||||
throw new UnsupportedOperationException("Can not set value to multi names.");
|
||||
}
|
||||
|
||||
|
@ -55,8 +55,8 @@ public class NameNode implements Node {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(final Object bean, final Object value) {
|
||||
DynaBean.of(bean).set(this.name, value);
|
||||
public Object setValue(final Object bean, final Object value) {
|
||||
return DynaBean.of(bean).set(this.name, value).getBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,6 +31,7 @@ public interface Node {
|
||||
*
|
||||
* @param bean bean对象
|
||||
* @param value 节点值
|
||||
* @return bean对象。如果在原Bean对象基础上设置值,返回原Bean,否则返回新的Bean
|
||||
*/
|
||||
void setValue(Object bean, Object value);
|
||||
Object setValue(Object bean, Object value);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public class RangeNode implements Node {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(final Object bean, final Object value) {
|
||||
public Object setValue(final Object bean, final Object value) {
|
||||
throw new UnsupportedOperationException("Can not set value with step name.");
|
||||
}
|
||||
|
||||
|
@ -12,26 +12,21 @@
|
||||
|
||||
package org.dromara.hutool.core.map;
|
||||
|
||||
import org.dromara.hutool.core.bean.BeanPathOld;
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.bean.copier.CopyOptions;
|
||||
import org.dromara.hutool.core.bean.path.BeanPath;
|
||||
import org.dromara.hutool.core.collection.set.SetUtil;
|
||||
import org.dromara.hutool.core.convert.Convert;
|
||||
import org.dromara.hutool.core.exception.CloneException;
|
||||
import org.dromara.hutool.core.lang.Assert;
|
||||
import org.dromara.hutool.core.func.LambdaInfo;
|
||||
import org.dromara.hutool.core.func.LambdaUtil;
|
||||
import org.dromara.hutool.core.func.SerFunction;
|
||||
import org.dromara.hutool.core.func.SerSupplier;
|
||||
import org.dromara.hutool.core.lang.Assert;
|
||||
import org.dromara.hutool.core.lang.getter.TypeGetter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 字典对象,扩充了LinkedHashMap中的方法
|
||||
@ -423,12 +418,12 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
|
||||
* @param <T> 目标类型
|
||||
* @param expression 表达式
|
||||
* @return 对象
|
||||
* @see BeanPathOld#get(Object)
|
||||
* @see BeanPath#getValue(Object)
|
||||
* @since 5.7.14
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getByPath(final String expression) {
|
||||
return (T) BeanPathOld.of(expression).get(this);
|
||||
return (T) BeanPath.of(expression).getValue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -453,7 +448,7 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
|
||||
* @param expression 表达式
|
||||
* @param resultType 返回值类型
|
||||
* @return 对象
|
||||
* @see BeanPathOld#get(Object)
|
||||
* @see BeanPath#getValue(Object)
|
||||
* @since 5.7.14
|
||||
*/
|
||||
public <T> T getByPath(final String expression, final Type resultType) {
|
||||
|
@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* https://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.bean;
|
||||
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.lang.test.bean.ExamInfoDict;
|
||||
import org.dromara.hutool.core.lang.test.bean.UserInfoDict;
|
||||
import org.dromara.hutool.core.map.Dict;
|
||||
import lombok.Data;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link BeanPathOld} 单元测试
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class BeanPathTest {
|
||||
|
||||
Map<String, Object> tempMap;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
// ------------------------------------------------- 考试信息列表
|
||||
final ExamInfoDict examInfoDict = new ExamInfoDict();
|
||||
examInfoDict.setId(1);
|
||||
examInfoDict.setExamType(0);
|
||||
examInfoDict.setAnswerIs(1);
|
||||
|
||||
final ExamInfoDict examInfoDict1 = new ExamInfoDict();
|
||||
examInfoDict1.setId(2);
|
||||
examInfoDict1.setExamType(0);
|
||||
examInfoDict1.setAnswerIs(0);
|
||||
|
||||
final ExamInfoDict examInfoDict2 = new ExamInfoDict();
|
||||
examInfoDict2.setId(3);
|
||||
examInfoDict2.setExamType(1);
|
||||
examInfoDict2.setAnswerIs(0);
|
||||
|
||||
final List<ExamInfoDict> examInfoDicts = new ArrayList<>();
|
||||
examInfoDicts.add(examInfoDict);
|
||||
examInfoDicts.add(examInfoDict1);
|
||||
examInfoDicts.add(examInfoDict2);
|
||||
|
||||
// ------------------------------------------------- 用户信息
|
||||
final UserInfoDict userInfoDict = new UserInfoDict();
|
||||
userInfoDict.setId(1);
|
||||
userInfoDict.setPhotoPath("yx.mm.com");
|
||||
userInfoDict.setRealName("张三");
|
||||
userInfoDict.setExamInfoDict(examInfoDicts);
|
||||
|
||||
tempMap = new HashMap<>();
|
||||
tempMap.put("userInfo", userInfoDict);
|
||||
tempMap.put("flag", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beanPathTest1() {
|
||||
final BeanPathOld pattern = new BeanPathOld("userInfo.examInfoDict[0].id");
|
||||
Assertions.assertEquals("userInfo", pattern.patternParts.get(0));
|
||||
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
|
||||
Assertions.assertEquals("0", pattern.patternParts.get(2));
|
||||
Assertions.assertEquals("id", pattern.patternParts.get(3));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beanPathTest2() {
|
||||
final BeanPathOld pattern = new BeanPathOld("[userInfo][examInfoDict][0][id]");
|
||||
Assertions.assertEquals("userInfo", pattern.patternParts.get(0));
|
||||
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
|
||||
Assertions.assertEquals("0", pattern.patternParts.get(2));
|
||||
Assertions.assertEquals("id", pattern.patternParts.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beanPathTest3() {
|
||||
final BeanPathOld pattern = new BeanPathOld("['userInfo']['examInfoDict'][0]['id']");
|
||||
Assertions.assertEquals("userInfo", pattern.patternParts.get(0));
|
||||
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
|
||||
Assertions.assertEquals("0", pattern.patternParts.get(2));
|
||||
Assertions.assertEquals("id", pattern.patternParts.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTest() {
|
||||
final BeanPathOld pattern = BeanPathOld.of("userInfo.examInfoDict[0].id");
|
||||
final Object result = pattern.get(tempMap);
|
||||
Assertions.assertEquals(1, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTest() {
|
||||
final BeanPathOld pattern = BeanPathOld.of("userInfo.examInfoDict[0].id");
|
||||
pattern.set(tempMap, 2);
|
||||
final Object result = pattern.get(tempMap);
|
||||
Assertions.assertEquals(2, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMapTest () {
|
||||
final BeanPathOld pattern = BeanPathOld.of("userInfo[id, photoPath]");
|
||||
@SuppressWarnings("unchecked")
|
||||
final Map<String, Object> result = (Map<String, Object>)pattern.get(tempMap);
|
||||
Assertions.assertEquals(1, result.get("id"));
|
||||
Assertions.assertEquals("yx.mm.com", result.get("photoPath"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getKeyWithDotTest () {
|
||||
final Map<String, Object> dataMap = new HashMap<>(16);
|
||||
dataMap.put("aa", "value0");
|
||||
dataMap.put("aa.bb.cc", "value111111");// key 是类名 格式 带 ' . '
|
||||
|
||||
final BeanPathOld pattern = BeanPathOld.of("'aa.bb.cc'");
|
||||
Assertions.assertEquals("value111111", pattern.get(dataMap));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compileTest(){
|
||||
final BeanPathOld of = BeanPathOld.of("'abc.dd'.ee.ff'.'");
|
||||
Assertions.assertEquals("abc.dd", of.getPatternParts().get(0));
|
||||
Assertions.assertEquals("ee", of.getPatternParts().get(1));
|
||||
Assertions.assertEquals("ff.", of.getPatternParts().get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issue2362Test() {
|
||||
final Map<String, Object> map = new HashMap<>();
|
||||
|
||||
BeanPathOld beanPath = BeanPathOld.of("list[0].name");
|
||||
beanPath.set(map, "张三");
|
||||
Assertions.assertEquals("{list=[{name=张三}]}", map.toString());
|
||||
|
||||
map.clear();
|
||||
beanPath = BeanPathOld.of("list[1].name");
|
||||
beanPath.set(map, "张三");
|
||||
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
|
||||
|
||||
map.clear();
|
||||
beanPath = BeanPathOld.of("list[0].1.name");
|
||||
beanPath.set(map, "张三");
|
||||
Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putTest() {
|
||||
final Map<String, Object> map = new HashMap<>();
|
||||
|
||||
final BeanPathOld beanPath = BeanPathOld.of("list[1].name");
|
||||
beanPath.set(map, "张三");
|
||||
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putByPathTest() {
|
||||
final Dict dict = new Dict();
|
||||
BeanPathOld.of("aa.bb").set(dict, "BB");
|
||||
Assertions.assertEquals("{aa={bb=BB}}", dict.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendArrayTest(){
|
||||
// issue#3008@Github
|
||||
final MyUser myUser = new MyUser();
|
||||
BeanPathOld.of("hobby[0]").set(myUser, "LOL");
|
||||
BeanPathOld.of("hobby[1]").set(myUser, "KFC");
|
||||
BeanPathOld.of("hobby[2]").set(myUser, "COFFE");
|
||||
|
||||
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getHobby()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendArrayTest2(){
|
||||
// issue#3008@Github
|
||||
final MyUser2 myUser = new MyUser2();
|
||||
BeanPathOld.of("myUser.hobby[0]").set(myUser, "LOL");
|
||||
BeanPathOld.of("myUser.hobby[1]").set(myUser, "KFC");
|
||||
BeanPathOld.of("myUser.hobby[2]").set(myUser, "COFFE");
|
||||
|
||||
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getMyUser().getHobby()));
|
||||
}
|
||||
|
||||
@Data
|
||||
static class MyUser {
|
||||
private String[] hobby;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class MyUser2 {
|
||||
private MyUser myUser;
|
||||
}
|
||||
}
|
@ -12,10 +12,16 @@
|
||||
|
||||
package org.dromara.hutool.core.bean;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.hutool.core.annotation.Alias;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.bean.copier.CopyOptions;
|
||||
import org.dromara.hutool.core.bean.copier.ValueProvider;
|
||||
import org.dromara.hutool.core.bean.path.BeanPath;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.collection.set.SetUtil;
|
||||
import org.dromara.hutool.core.map.MapBuilder;
|
||||
@ -23,11 +29,6 @@ import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -767,8 +768,8 @@ public class BeanUtilTest {
|
||||
|
||||
testPojo.setTestPojo2List(new TestPojo2[]{testPojo2, testPojo3});
|
||||
|
||||
final BeanPathOld beanPath = BeanPathOld.of("testPojo2List.age");
|
||||
final Object o = beanPath.get(testPojo);
|
||||
final BeanPath beanPath = BeanPath.of("testPojo2List.age");
|
||||
final Object o = beanPath.getValue(testPojo);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(2), ArrayUtil.get(o, 0));
|
||||
Assertions.assertEquals(Integer.valueOf(3), ArrayUtil.get(o, 1));
|
||||
|
@ -12,8 +12,11 @@
|
||||
|
||||
package org.dromara.hutool.core.bean.path;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.lang.test.bean.ExamInfoDict;
|
||||
import org.dromara.hutool.core.lang.test.bean.UserInfoDict;
|
||||
import org.dromara.hutool.core.map.Dict;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -113,4 +116,52 @@ public class BeanPathGetOrSetValueTest {
|
||||
beanPath.setValue(map, "张三");
|
||||
Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putTest() {
|
||||
final Map<String, Object> map = new HashMap<>();
|
||||
|
||||
final BeanPath beanPath = BeanPath.of("list[1].name");
|
||||
beanPath.setValue(map, "张三");
|
||||
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putByPathTest() {
|
||||
final Dict dict = new Dict();
|
||||
BeanPath.of("aa.bb").setValue(dict, "BB");
|
||||
Assertions.assertEquals("{aa={bb=BB}}", dict.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendArrayTest(){
|
||||
// issue#3008@Github
|
||||
final MyUser myUser = new MyUser();
|
||||
BeanPath.of("hobby[0]").setValue(myUser, "LOL");
|
||||
BeanPath.of("hobby[1]").setValue(myUser, "KFC");
|
||||
BeanPath.of("hobby[2]").setValue(myUser, "COFFE");
|
||||
|
||||
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getHobby()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appendArrayTest2(){
|
||||
// issue#3008@Github
|
||||
final MyUser2 myUser = new MyUser2();
|
||||
BeanPath.of("myUser.hobby[0]").setValue(myUser, "LOL");
|
||||
BeanPath.of("myUser.hobby[1]").setValue(myUser, "KFC");
|
||||
BeanPath.of("myUser.hobby[2]").setValue(myUser, "COFFE");
|
||||
|
||||
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getMyUser().getHobby()));
|
||||
}
|
||||
|
||||
@Data
|
||||
static class MyUser {
|
||||
private String[] hobby;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class MyUser2 {
|
||||
private MyUser myUser;
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,6 @@
|
||||
package org.dromara.hutool.core.reflect;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 反射工具类单元测试
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
package org.dromara.hutool.json;
|
||||
|
||||
import org.dromara.hutool.core.bean.BeanPathOld;
|
||||
import org.dromara.hutool.core.bean.path.BeanPath;
|
||||
import org.dromara.hutool.core.convert.ConvertException;
|
||||
import org.dromara.hutool.core.convert.Converter;
|
||||
import org.dromara.hutool.core.lang.mutable.MutableEntry;
|
||||
@ -64,11 +64,11 @@ public interface JSON extends Converter, Cloneable, Serializable {
|
||||
*
|
||||
* @param expression 表达式
|
||||
* @return 对象
|
||||
* @see BeanPathOld#get(Object)
|
||||
* @see BeanPath#getValue(Object)
|
||||
* @since 4.0.6
|
||||
*/
|
||||
default Object getByPath(final String expression) {
|
||||
return BeanPathOld.of(expression).get(this);
|
||||
return BeanPath.of(expression).getValue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,7 +93,7 @@ public interface JSON extends Converter, Cloneable, Serializable {
|
||||
* @param value 值
|
||||
*/
|
||||
default void putByPath(final String expression, final Object value) {
|
||||
BeanPathOld.of(expression).set(this, value);
|
||||
BeanPath.of(expression).setValue(this, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,11 +118,11 @@ public interface JSON extends Converter, Cloneable, Serializable {
|
||||
* @param expression 表达式
|
||||
* @param resultType 返回值类型
|
||||
* @return 对象
|
||||
* @see BeanPathOld#get(Object)
|
||||
* @see BeanPath#getValue(Object)
|
||||
* @since 4.0.6
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T getByPath(final String expression, final Type resultType){
|
||||
default <T> T getByPath(final String expression, final Type resultType) {
|
||||
return (T) config().getConverter().convert(resultType, getByPath(expression));
|
||||
}
|
||||
|
||||
|
@ -340,7 +340,7 @@ public class JWT implements RegisteredPayload<JWT> {
|
||||
*
|
||||
* <p>此方法会补充如下的header:</p>
|
||||
* <ul>
|
||||
* <li>当用户未定义"typ"时,赋默认值:"JWT"</li>
|
||||
* <li>当用户未定义"typ"时,不设置默认值</li>
|
||||
* <li>当用户未定义"alg"时,根据传入的{@link JWTSigner}对象类型,赋值对应ID</li>
|
||||
* </ul>
|
||||
*
|
||||
@ -350,12 +350,6 @@ public class JWT implements RegisteredPayload<JWT> {
|
||||
public String sign(final JWTSigner signer) {
|
||||
Assert.notNull(signer, () -> new JWTException("No Signer provided!"));
|
||||
|
||||
// 检查tye信息
|
||||
final String type = (String) this.header.getClaim(JWTHeader.TYPE);
|
||||
if (StrUtil.isBlank(type)) {
|
||||
this.header.setType("JWT");
|
||||
}
|
||||
|
||||
// 检查头信息中是否有算法信息
|
||||
final String algorithm = (String) this.header.getClaim(JWTHeader.ALGORITHM);
|
||||
if (StrUtil.isBlank(algorithm)) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
package org.dromara.hutool.json.jwt;
|
||||
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.json.jwt.signers.JWTSigner;
|
||||
|
||||
import java.util.Map;
|
||||
@ -29,7 +30,7 @@ public class JWTUtil {
|
||||
* @return JWT Token
|
||||
*/
|
||||
public static String createToken(final Map<String, Object> payload, final byte[] key) {
|
||||
return createToken(null, payload, key);
|
||||
return createToken(MapUtil.of(JWTHeader.TYPE, "JWT"), payload, key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,18 +27,19 @@ import java.util.*;
|
||||
public class JWTTest {
|
||||
|
||||
@Test
|
||||
public void createHs256Test(){
|
||||
public void createHs256Test() {
|
||||
final byte[] key = "1234567890".getBytes();
|
||||
final JWT jwt = JWT.of()
|
||||
.setPayload("sub", "1234567890")
|
||||
.setPayload("name", "looly")
|
||||
.setPayload("admin", true)
|
||||
.setExpiresAt(DateUtil.parse("2022-01-01"))
|
||||
.setKey(key);
|
||||
.setHeader(JWTHeader.TYPE, "JWT")
|
||||
.setPayload("sub", "1234567890")
|
||||
.setPayload("name", "looly")
|
||||
.setPayload("admin", true)
|
||||
.setExpiresAt(DateUtil.parse("2022-01-01"))
|
||||
.setKey(key);
|
||||
|
||||
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
|
||||
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWUsImV4cCI6MTY0MDk2NjQwMH0." +
|
||||
"bXlSnqVeJXWqUIt7HyEhgKNVlIPjkumHlAwFY-5YCtk";
|
||||
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWUsImV4cCI6MTY0MDk2NjQwMH0." +
|
||||
"bXlSnqVeJXWqUIt7HyEhgKNVlIPjkumHlAwFY-5YCtk";
|
||||
|
||||
final String token = jwt.sign();
|
||||
Assertions.assertEquals(rightToken, token);
|
||||
@ -47,10 +48,10 @@ public class JWTTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseTest(){
|
||||
public void parseTest() {
|
||||
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
|
||||
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
|
||||
"U2aQkC2THYV9L0fTN-yBBI7gmo5xhmvMhATtu8v0zEA";
|
||||
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
|
||||
"U2aQkC2THYV9L0fTN-yBBI7gmo5xhmvMhATtu8v0zEA";
|
||||
|
||||
final JWT jwt = JWT.of(rightToken);
|
||||
|
||||
@ -68,15 +69,16 @@ public class JWTTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createNoneTest(){
|
||||
public void createNoneTest() {
|
||||
final JWT jwt = JWT.of()
|
||||
.setPayload("sub", "1234567890")
|
||||
.setPayload("name", "looly")
|
||||
.setPayload("admin", true)
|
||||
.setSigner(JWTSignerUtil.none());
|
||||
.setHeader(JWTHeader.TYPE, "JWT")
|
||||
.setPayload("sub", "1234567890")
|
||||
.setPayload("name", "looly")
|
||||
.setPayload("admin", true)
|
||||
.setSigner(JWTSignerUtil.none());
|
||||
|
||||
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0." +
|
||||
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWV9.";
|
||||
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWV9.";
|
||||
|
||||
final String token = jwt.sign();
|
||||
Assertions.assertEquals(rightToken, token);
|
||||
@ -88,8 +90,8 @@ public class JWTTest {
|
||||
* 必须定义签名器
|
||||
*/
|
||||
@Test
|
||||
public void needSignerTest(){
|
||||
Assertions.assertThrows(JWTException.class, ()->{
|
||||
public void needSignerTest() {
|
||||
Assertions.assertThrows(JWTException.class, () -> {
|
||||
final JWT jwt = JWT.of()
|
||||
.setPayload("sub", "1234567890")
|
||||
.setPayload("name", "looly")
|
||||
@ -100,10 +102,10 @@ public class JWTTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyTest(){
|
||||
public void verifyTest() {
|
||||
final String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
|
||||
"eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjQwMDQ4MjIsInVzZXJJZCI6MSwiYXV0aG9yaXRpZXMiOlsiUk9MRV_op5LoibLkuozlj7ciLCJzeXNfbWVudV8xIiwiUk9MRV_op5LoibLkuIDlj7ciLCJzeXNfbWVudV8yIl0sImp0aSI6ImQ0YzVlYjgwLTA5ZTctNGU0ZC1hZTg3LTVkNGI5M2FhNmFiNiIsImNsaWVudF9pZCI6ImhhbmR5LXNob3AifQ." +
|
||||
"aixF1eKlAKS_k3ynFnStE7-IRGiD5YaqznvK2xEjBew";
|
||||
"eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjQwMDQ4MjIsInVzZXJJZCI6MSwiYXV0aG9yaXRpZXMiOlsiUk9MRV_op5LoibLkuozlj7ciLCJzeXNfbWVudV8xIiwiUk9MRV_op5LoibLkuIDlj7ciLCJzeXNfbWVudV8yIl0sImp0aSI6ImQ0YzVlYjgwLTA5ZTctNGU0ZC1hZTg3LTVkNGI5M2FhNmFiNiIsImNsaWVudF9pZCI6ImhhbmR5LXNob3AifQ." +
|
||||
"aixF1eKlAKS_k3ynFnStE7-IRGiD5YaqznvK2xEjBew";
|
||||
|
||||
final boolean verify = JWT.of(token).setKey(ByteUtil.toUtf8Bytes("123456")).verify();
|
||||
Assertions.assertTrue(verify);
|
||||
@ -114,6 +116,7 @@ public class JWTTest {
|
||||
private String name;
|
||||
private Integer age;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void payloadTest() {
|
||||
|
||||
@ -130,6 +133,7 @@ public class JWTTest {
|
||||
map.put("test2", "2");
|
||||
final Map<String, Object> payload = new HashMap<String, Object>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
{
|
||||
put("username", username);
|
||||
put("bean", bean);
|
||||
@ -161,11 +165,11 @@ public class JWTTest {
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void getDateTest(){
|
||||
public void getDateTest() {
|
||||
final String token = JWT.of()
|
||||
.setIssuedAt(DateUtil.parse("2022-02-02"))
|
||||
.setKey("123456".getBytes())
|
||||
.sign();
|
||||
.setIssuedAt(DateUtil.parse("2022-02-02"))
|
||||
.setKey("123456".getBytes())
|
||||
.sign();
|
||||
|
||||
// 签发时间早于被检查的时间
|
||||
final Date date = JWT.of(token).getPayload().getClaimsJson().getDate(JWTPayload.ISSUED_AT);
|
||||
@ -173,7 +177,7 @@ public class JWTTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issue2581Test(){
|
||||
public void issue2581Test() {
|
||||
final Map<String, Object> map = new HashMap<>();
|
||||
map.put("test2", 22222222222222L);
|
||||
final JWTSigner jwtSigner = JWTSignerUtil.createSigner(AlgorithmUtil.getAlgorithm("HS256"), Base64.getDecoder().decode("abcdefghijklmn"));
|
||||
@ -183,17 +187,17 @@ public class JWTTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLongTest(){
|
||||
public void getLongTest() {
|
||||
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
|
||||
+ ".eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhZG1pbiIsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjoxNjc4Mjg1NzEzOTM1LCJyblN0ciI6IkVuMTczWFhvWUNaaVZUWFNGOTNsN1pabGtOalNTd0pmIn0"
|
||||
+ ".wRe2soTaWYPhwcjxdzesDi1BgEm9D61K-mMT3fPc4YM";
|
||||
+ ".eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhZG1pbiIsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjoxNjc4Mjg1NzEzOTM1LCJyblN0ciI6IkVuMTczWFhvWUNaaVZUWFNGOTNsN1pabGtOalNTd0pmIn0"
|
||||
+ ".wRe2soTaWYPhwcjxdzesDi1BgEm9D61K-mMT3fPc4YM";
|
||||
|
||||
final JWT jwt = JWTUtil.parseToken(rightToken);
|
||||
|
||||
Assertions.assertEquals(
|
||||
"{\"loginType\":\"login\",\"loginId\":\"admin\",\"device\":\"default-device\"," +
|
||||
"\"eff\":1678285713935,\"rnStr\":\"En173XXoYCZiVTXSF93l7ZZlkNjSSwJf\"}",
|
||||
jwt.getPayloads().toString());
|
||||
"{\"loginType\":\"login\",\"loginId\":\"admin\",\"device\":\"default-device\"," +
|
||||
"\"eff\":1678285713935,\"rnStr\":\"En173XXoYCZiVTXSF93l7ZZlkNjSSwJf\"}",
|
||||
jwt.getPayloads().toString());
|
||||
Assertions.assertEquals(Long.valueOf(1678285713935L), jwt.getPayloads().getLong("eff"));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user