GVKun编程网logo

JSON 编辑器实现代码(好用的json编辑器)

20

在本文中,我们将给您介绍关于JSON编辑器实现代码的详细内容,并且为您解答好用的json编辑器的相关问题,此外,我们还将为您提供关于Ajax+Json级联菜单实现代码_json、Asp.NetCore

在本文中,我们将给您介绍关于JSON 编辑器实现代码的详细内容,并且为您解答好用的json编辑器的相关问题,此外,我们还将为您提供关于Ajax+Json 级联菜单实现代码_json、Asp.Net Core 使用Monaco Editor 实现代码编辑器功能、C#解析json文件的实现代码、Electron Markdown编辑器实战:资源管理器实现的知识。

本文目录一览:

JSON 编辑器实现代码(好用的json编辑器)

JSON 编辑器实现代码(好用的json编辑器)

大家可以输入或者粘贴一些JSON数据来校验是否正确。
按Tab键自动全文缩进格式化。

 
JSON 编辑器
核心代码
复制代码 代码如下:

<script type="text/javascript"><!--
/*
main Object
*/
JE={
data:{},/* 关联数据 */
code:false,/* 格式化后的代码 */
oldCode:[],/* 历史代码 */
editUI:null,/* 关联编辑框 */
msgUI:null,/* 信息显示窗口 */
treeUI:null,/* 树窗口 */
name:''JE'',/* 实例名 */
root:''<b>JSON无忧</b>'',/* 根节点标题 */
checkbox:0,/* 是否添加复框 */
hot:null,/* 选中节点 */
indent:'' '',/*缩进符''\t''或者4个'' ''*/
firstUp:true,/*第一次自动刷新*/
onclick:Array,/*树点击通知*/
countInfo:'''',/*统计信息*/
formating:false,/* 格式化中(禁止重构树) */
ico:{/* 树图标 */
//文件夹结构线
r0:''img?uuid=20166b3094daba4bc6e18817b8301b093a'',
r0c:''img?uuid=208018827ed877e31810e838d33e4ac2b0'',
r1:''img?uuid=2094c793496278bde84be80bb6cb2117f5'',
r1c:''img?uuid=205f2329c537dcfd32b5f33bf642f76fa6'',
r2:''img?uuid=20af62be7c197cae64d3e533f7aaf28c25'',
r2c:''img?uuid=20a005983863e5bc8544cecd8b7f82fcdb'',
//前缀图片
nl:''img?uuid=20f508bdc9bb8d98f4529e0fa2475b91bb'',
vl:''img?uuid=20d5de63f4e6927bbb23c377bd1073d26f'',
//文件结构线
f1:''img?uuid=202ccc639afd44130a3946e9837672479c'',
f2:''img?uuid=20900ceb0053a2f7bd07a22337c4e4c72c'',
root:''img?uuid=2069cf3fd1e53a2bb365f771eac65b50a2'',
//文件夹
arr:''img?uuid=20b7d6e86f4f288ea097c10b1c0d7f4b6b'',
arrc:''img?uuid=20b7d6e86f4f288ea097c10b1c0d7f4b6b'',
obj:''img?uuid=20c34d1d5d5a4639061e08165c61a97e63'',
objc:''img?uuid=20c34d1d5d5a4639061e08165c61a97e63'',
//文件
arr2:''img?uuid=20327500502b71ed0278a0cc1bf8f8bb41'',
obj2:''img?uuid=203346cafeedac1fb7628bc886b9b7fb47''
},
toTree:function(){/* JSON转换为树HTML,同时格式化代码 */
var draw=[],This=this,ico=this.ico;
JE.firstUp=false;/*完成首次自动构造*/
var notify=function(prefix/*前缀HTML*/,lastParent/*父是否是尾节点(确定图标是空白|结构线)*/,name /*节点名*/,value/*节点值*/,formObj/* 父是否是对象(确定子图标) */){/* 构造子节点 */
var rl=prefix==''''?ico.r0:lastParent?ico.r1:ico.r2;/* 配置根节点图标 */
if(value&&value.constructor==Array){/* 处理数组节点 */
draw.push(''<dl><dt>'',This.draw(prefix,rl,ico.arr,name,''''),''</dt><dd>'');/* 绘制文件夹 */
for (var i=0;i<value.length;i++)
notify(prefix+This.img(lastParent?ico.nl:ico.vl),i==value.length-1,i,value[i]);
draw.push(''</dd></dl>'');
}else if(value&&typeof value==''object''){/* 处理对象节点 */
draw.push(''<dl><dt>'',This.draw(prefix,rl,ico.obj,name,''''),''</dt><dd>'');/* 绘制文件夹 */
var len=0,i=0;
for(var key in value)len++;/* 获取对象成员总数 */
for(var key in value)notify(prefix+This.img(lastParent?ico.nl:ico.vl),++i==len,key,value[key],true);
draw.push(''</dd></dl>'');
}else{/* 处理叶节点(绘制文件) */
draw.push(''<dl><dt>'',This.draw(prefix,lastParent?ico.f1:ico.f2,formObj?ico.obj2:ico.arr2,name,value),''</dt></dl>'');
};
};/* 不是[]或者{}不绘制 */
if(typeof this.data==''object''){notify('''',true,this.root,this.data);};
if(this.treeUI)this.treeUI.innerHTML=draw.join('''');/* 显示在树窗口 */
this.msg(''成功构造树视图!'');
},
draw:function(prevfix,line,ico,name,value){/* 子项HTML构造器 */
var cmd=false,J=this.ico,imgName=false;
switch (line) {//传递切换图标
case J.r0:imgName=''r0'';break;
case J.r1:imgName=''r1'';break;
case J.r2:imgName=''r2'';
}
if(imgName)cmd='' onclick="''+this.name+''.show(this,\''''+imgName+''\'')" '';/* 加入折叠命令 */
var type=typeof name==''string''?''(对象成员)'':''(数组索引)'';
return prevfix+this.img(line,cmd)
+(this.checkbox?''<input type="checkbox" onclick="''+this.name+''.select(this)" />'':'''')
+this.img(ico)+'' <a href="javascript:void(0)" href="javascript:void(0)" onclick="''+this.name+''.click(this);" ''
+''key="''+name+''" val="''+value+''" >''
+name+type+(value==''''?'''':'' = '')+value+''</a>''
},
img:function(src,attr){/* img HTML构造 */
return ''<img src="''+src+''" src="''+src+''" ''+(attr||'''')+'' />'';
},
click:function(item){/* 子项点击统一回调 */
if(this.hot)this.hot.className='''';
this.hot=item;
this.hot.className=''hot'';/* 切换选中项 */
this.onclick(item);
},
format:function(txt,compress/*是否为压缩模式*/){/* 格式化JSON源码(对象转换为JSON文本) */
if(/^\s*$/.test(txt))return this.msg(''数据为空,无法格式化! '');
try{var data=eval(''(''+txt+'')'');}
catch(e){
JE.editUI.style.color=''red'';
return this.msg(''数据源语法错误,格式化失败! 错误信息: ''+e.description,''err'');
};
JE.editUI.style.color=''#000'';
var draw=[],last=false,This=this,line=compress?'''':''\n'',nodeCount=0,maxDepth=0;
var notify=function(name,value,isLast,indent/*缩进*/,formObj){
nodeCount++;/*节点计数*/
for (var i=0,tab='''';i<indent;i++ )tab+=This.indent;/* 缩进HTML */
tab=compress?'''':tab;/*压缩模式忽略缩进*/
maxDepth=++indent;/*缩进递增并记录*/
if(value&&value.constructor==Array){/*处理数组*/
draw.push(tab+(formObj?(''"''+name+''":''):'''')+''[''+line);/*缩进''['' 然后换行*/
for (var i=0;i<value.length;i++)
notify(i,value[i],i==value.length-1,indent,false);
draw.push(tab+'']''+(isLast?line:('',''+line)));/*缩进'']''换行,若非尾元素则添加逗号*/
}else if(value&&typeof value==''object''){/*处理对象*/
draw.push(tab+(formObj?(''"''+name+''":''):'''')+''{''+line);/*缩进''{'' 然后换行*/
var len=0,i=0;
for(var key in value)len++;
for(var key in value)notify(key,value[key],++i==len,indent,true);
draw.push(tab+''}''+(isLast?line:('',''+line)));/*缩进''}''换行,若非尾元素则添加逗号*/
}else{
if(typeof value==''string'')value=''"''+value+''"'';
draw.push(tab+(formObj?(''"''+name+''":''):'''')+value+(isLast?'''':'','')+line);
};
};
var isLast=true,indent=0;
notify('''',data,isLast,indent,false);
this.countInfo=''共处理节点<b>''+nodeCount+''</b>个,最大树深为<b>''+maxDepth+''</b>'';
return draw.join('''');
},
msg:function(text,type){/* 编辑状态或者错误通知 */
if(!this.msgUI)return alert(text);
with(new Date)var now=([getHours(),getMinutes(),getSeconds()].join('':'')).replace(/\b\d\b/g,''0$&'');
this.msgUI.innerHTML=''<div>[''+now+''] &nbsp;&nbsp;''+text.replace(/\n/g,''<br/>'')+''</div>'';
this.msgUI.className=type;
},
show:function (ico,id){/* 显隐树节点 */
var subView=ico.parentNode.parentNode.childNodes[1].style,J=this.ico;
if(subView.display==''none''){
subView.display='''';
ico.src=J[id];
}else{
subView.display=''none'';
ico.src=J[id+''c''];
};
},
select:function (sender){
var sub=sender.parentNode.parentNode.getElementsByTagName("INPUT");
for (var i=0;i<sub.length;i++ ) {sub[i].checked=sender.checked;}
}
};
JE.add=function(){
this.msg(''功能添加中...*_^'');
}
JE.editItem=function(){
this.msg(''功能添加中...*_^'');
}
JE.begin=function(){/* 设置UI控件关联响应 */
var $=function (id){return document.getElementById(id)};
/* 关联UI */
JE.editUI=$("json_eidit");
JE.msgUI=$("json_editInfo");
JE.treeUI=$("tree");
var updateUI=$("update");
var auto=$("autoUpdate");
var fontSize=$("fontSize");
/* 单击树子项 */
JE.onclick=function(item){
var key=''键名: <input value="''+item.getAttribute(''key'')+''" />'',
val='' 键值: ''+(item.getAttribute(''val'')==''''?''成员列表'':''<input value="''+item.getAttribute(''val'')+''" />''),
add=''<button onclick="''+this.name+''.add(this)">新增</button>'',
edit=''<button onclick="''+this.name+''.editItem(this)">修改</button>'';
JE.msg(key+val+add+edit,''info'');
}
/* 监听代码变化事件 */
JE.editUI.oninput=JE.editUI.onpropertychange=function (){
if(JE.formating)return;/* 格式化不刷新树 */
if(/^\s*$/.test(this.value))return JE.msg(''请输入JSON格式的代码!'');;
clearTimeout(JE.update);
try{JE.data=eval(''(''+this.value+'')'');
}catch(e){
JE.editUI.style.color=''red'';
return JE.msg("源代码有错误: "+e.description+'' , 如果正在编辑中, 请忽略此消息!'',''err'');
};
JE.editUI.style.color=''#000'';
if(auto.checked||JE.firstUp){/*若同步*/
JE.msg(''语法正确,正在重新构造树,请稍候...'',''busy'');
JE.update=setTimeout(function(){
JE.toTree();
},450);
}else{
JE.msg(''语法正确,请点击刷新,或者打开视图同步开关,或者继续编辑!'')
}
return true;
};
if(window.ActiveXObject)
document.execCommand("BackgroundImageCache", false, true);
/* 拦截Tab,自动格式化 */
JE.editUI.onkeydown=function (){
if(event.keyCode==9){$(''format_indent'').onclick();event.returnValue=false;};
JE.code=this.value;
}
/* 格式化 */
var format=function(compress){
var code=JE.format(JE.editUI.value,compress);
JE.formating=true;
if(code)JE.editUI.value=code;
JE.editUI.focus();
setTimeout(function(){JE.formating=false;},1000);
return code;
}
/* 工具栏按钮 */
$(''format_indent'').onclick=function (){if(format())JE.msg(''完成缩进风格转换,''+JE.countInfo)}
$(''format_compress'').onclick=function (){if(format(true)!=undefined)JE.msg(''完成紧凑风格转换,''+JE.countInfo);}
updateUI.onclick=function (){
JE.firstUp=true;
JE.editUI.onpropertychange()?JE.msg(''成功刷新视图!''):JE.msg(''数据有误,刷新失败!'',''err'')
JE.firstUp=false;
};
$(''clear_txt'').onclick=function (){JE.editUI.value=JE.treeUI.innerHTML='''';JE.editUI.focus();}
auto.onclick=function (){JE.msg(''自动同步视图功能''+(this.checked?''开启'':''关闭!''));};
/* 另存为 */
if(/*@cc_on !@*/true){$(''save_as'').style.display=''none''};
$(''save_as'').onclick=function (){
var d=document,w=d.createElement(''IFRAME'');
w.style.display="none";
d.body.appendChild(w);
setTimeout(function(){
var g=w.contentWindow.document;
g.charset = ''utf-8'';
g.body.innerHTML=JE.editUI.value;
g.execCommand("saveas",'''', "json.txt") ;
},1);
}
};
/* 从这里开始 */
window.onload=function (){
JE.begin();
}
// --></script>
您可能感兴趣的文章:
  • 学习js在线html(富文本,所见即所得)编辑器
  • 百度编辑器从Json对象中取值,完成初次渲染,在编辑器内画表格
  • 用js实现的DIV+CSS编辑器代码
  • js实现文本框宽度自适应文本宽度的方法
  • js限制文本框只能输入中文的方法
  • js实现文本框选中的方法
  • js显示文本框提示文字的方法
  • js实现文本框中输入文字页面中div层同步获取文本框内容的方法
  • JS限制文本框只能输入数字和字母方法
  • JS模仿编辑器实时改变文本框宽度和高度大小的方法

Ajax+Json 级联菜单实现代码_json

Ajax+Json 级联菜单实现代码_json

第一个下拉框:

复制代码 代码如下:



第二个下拉框:
复制代码 代码如下:



js代码:
复制代码 代码如下:



复制代码 代码如下:



//定义返回的参数
^subCategoryDictList\[\d+\]\.id,
^subCategoryDictList\[\d+\]\.name



Asp.Net Core 使用Monaco Editor 实现代码编辑器功能

Asp.Net Core 使用Monaco Editor 实现代码编辑器功能

在项目中经常有代码在线编辑的需求,比如修改基于Xml的配置文件,编辑Json格式的测试数据等。我们可以使用微软开源的在线代码编辑器Monaco Editor实现这些功能。Monaco Editor是著名的VSCode的前身,项目地址:https://microsoft.github.io/monaco-editor/。本文介绍在Asp.Net Core项目中使用Monaco Editor实现代码编辑器功能。

安装

可以使用npm下载moaco-editor:

npm install monaco-editor@0.31.1

我们需要的文件在node_modules/monaco-editor/min/vs目录中,将整个vs目录拷贝到Asp.Net Core Web项目的wwwroot/lib目录下:

在Program.cs中需要启动静态文件:

app.UseStaticFiles();

在布局页面中引入如下css和js:

<link data-name="vs/editor/editor.main" rel="stylesheet" href="~/lib/vs/editor/editor.main.css" rel="external nofollow"  />
    <script  src="~/lib/vs/loader.js"></script>
    <script  src="~/lib/vs/editor/editor.main.nls.js"></script>
    <script src="~/lib/vs/editor/editor.main.js"></script>

基本的环境设置就完成了。

基本功能

现在可以在页面中实现编辑器的基本功能,在页面中增加一个div,作为编辑器容器:

<div id="container"></div>

然后增加编辑器的js代码:

<script>
    $(document).ready(function () {
        require.config({ paths: { ''vs'': ''/lib/vs'' } });
        monaco.editor.create(document.getElementById(''container''), {
            value: "function sayHello(){\n console.write(''Hello World'');\n}",
            language: ''javascript''
        });
    })
</script>

设置

编辑器有多种设置,比如是否显示代码行、显示样式等等,设置完成后,可以使用updateOptions修改设置,示例代码如下:

        var editor = monaco.editor.create(document.getElementById(''container''), {
            value: "function sayHello(){\n console.write(''Hello World'');\n}",
            language: ''javascript'',
            lineNumbers: ''on'',
            roundedSelection: false,
            scrollBeyondLastLine: false,
            readOnly: false,
            theme: ''vs-dark''
        });

代码比较

monaco editor的一个强大的功能是文字比较功能,可以将两段文字进行比较:

<script>
    require.config({ paths: { ''vs'': ''/lib/vs'' } });
    var originalModel = monaco.editor.createModel(
        ''删除行\n行一\n行二\n行三内容\n更多内容'',
        ''text/plain''
    );
    var modifiedModel = monaco.editor.createModel(
        ''\n行一\n行二\n行三\n更多内容\n增加行'',
        ''text/plain''
    );
    var diffEditor = monaco.editor.createDiffEditor(document.getElementById(''container''), {
        // You can optionally disable the resizing
        enableSplitViewResizing: false
    });
    diffEditor.setModel({
        original: originalModel,
        modified: modifiedModel
    });
</script>

效果如下:

自定义语言

monaco editor 支持常见的几乎所有编程语言,在编辑这些语言的代码时可以高亮显示关键字,同时也支持对自定义语言的扩展。其功能非常强大,同时配置起来也比较复杂,这里只举个简单的例子,说明如何使用。

这里使用的“语言”很简单,目的是记录中国象棋的棋谱,关键字只有代表“车马炮”等棋子的大写汉语拼音,运算符只有代表向前、向后和平行移动的“++”、“--”和“==”,还有就是注释。
使用自定义语言,首先要注册这个语言的id:

        monaco.languages.register({ id: ''mylang'' });

然后设置语言的Token Provider:

 monaco.languages.setMonarchTokensProvider(''mylang'', getlang());

这样就可以在编辑器中使用这种自定义语言了,下面是完整的代码:

@page
@model CustomLanguageModel
@{
    ViewData["Title"] = "自定义语言";
}
<h1>@ViewData["Title"]</h1>
<div id="container"></div>
<script>
    var require = { paths: { vs: ''/lib/vs'' } };
</script>
@section Scripts
{
<script>
    $(document).ready(function () {
        monaco.languages.register({ id: ''mylang'' });
        monaco.languages.setMonarchTokensProvider(''mylang'', getlang());

        var sampleEditor=monaco.editor.create(document.getElementById(''container''), {
            model:null
        }
        );
        setTimeout(function(){
             var model=monaco.editor.createModel(''// 炮二平五 \nPAO 2 == 5 \n//马八进七 \nMA 8 ++ 7'', ''mylang'');
             sampleEditor.setModel(model);
        },1);
    });
    function getlang(){
        return {
            //车马炮相士将帅兵卒
          keywords: [ ''JU'', ''MA'', ''PAO'', ''XIANG'', ''SHI'', ''JIANG'', ''SHUAI'', ''BING'', ''ZU'' ],
          //++ 进 --退 ==平 
          operators: [ ''++'', ''--'', ''==''  ],
          symbols:  /[=><!~?:&|+\-*\/\^%]+/,
          // The main tokenizer for our languages
          tokenizer: {
            root: [
              [/[A-Z][\w\$]*/, {cases: { ''@@keywords'': ''keyword'' }}],
              { include: ''@@whitespace'' },
              [/@@symbols/, { cases: { ''@@operators'': ''operator''} } ],
              [/\d./, ''number''],
            ],
            comment: [
              [/[^\/*]+/, ''comment'' ],
              [/\/\*/,    ''comment'', ''@@push'' ],
              ["\\*/",    ''comment'', ''@@pop''  ],
              [/[\/*]/,   ''comment'' ]
            ],
            whitespace: [
              [/[ \t\r\n]+/, ''white''],
              [/\/\*/,       ''comment'', ''@@comment'' ],
              [/\/\/.*$/,    ''comment''],
            ],
          },
        };
    }
</script>
}}

效果如下:

本文的示例项目已经上传到github: https://github.com/zhenl/monacoEditorDotNet

到此这篇关于Asp.Net Core 使用Monaco Editor 实现代码编辑器的文章就介绍到这了,更多相关Asp.Net Core代码编辑器内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

您可能感兴趣的文章:

C#解析json文件的实现代码

C#解析json文件的实现代码

c# 解析 json

  JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript语法标准的一个子集。 JSON采用完全独立于语言的文本格式,可以很容易在各种网络、平台和程序之间传输。JSON的语法很简单,易于人阅读和编写,同时也易于机器解析和生成。

JSON与XML的比较

◆可读性
  JSON和XML的可读性相比较而言,由于XML提供辅助的标签,更加适合人阅读和理解。
◆文件大小与传输
  XML允许使用方便的标签,所以文件尺寸是要比JSON大的。而且JSON源于Javascript,所以天生的主战场是Javascript与网络,在这里,JSON有着XML无法赶超的优势。

JSON语法

1. JSON 语法是 JavaScript 对象表示法语法的子集。

•数据在名称/值对中:名称是字符串,使用双引号表示。值可以是:数字(整数或浮点数),字符串(在双引号中),数组(在方括号中),对象(在花括号中),true/false/null。
•数据由逗号分隔:
•花括号保存对象:对象可以包含各种数据,包括数组。
•方括号保存数组:数字可以包含对象。

例如:

{
    "employees": [
        {
            "firstName": "Bill",
            "lastName": "Gates"
        },
        {
            "firstName": "George",
            "lastName": "Bush"
        }
    ]
}
登录后复制

2. 如果JSON中含有转义字符,则需要转义。例如文件路径中需要使用"\\"而不是"\"。例如:{ "file":"C:\\a.txt"}。

.NET操作JSON

  JSON文件读入到内存中就是字符串,.NET操作JSON就是生成与解析JSON字符串。操作JSON通常有以下几种方式:

1. 原始方式:自己按照JSON的语法格式,写代码直接操作JSON字符串。如非必要,应该很少人会走这条路,从头再来的。

2. 通用方式【★★★★★】:

这种方式是使用开源的类库Newtonsoft.Json(下载地址http://json.codeplex.com/)。下载后加入工程就能用。通常可以使用JObject, JsonReader, JsonWriter处理。这种方式最通用,也最灵活,可以随时修改不爽的地方。
(1)使用JsonReader读Json字符串:

string jsonText = @"{""input"" : ""value"", ""output"" : ""result""}";
JsonReader reader = new JsonTextReader(new StringReader(jsonText));
while (reader.Read())
{
    Console.WriteLine(reader.TokenType + "\t\t" + reader.ValueType + "\t\t" + reader.Value);
}
登录后复制

(2)使用JsonWriter写字符串:

StringWriter sw = new StringWriter();
JsonWriter writer = new JsonTextWriter(sw);
writer.WriteStartObject();
writer.WritePropertyName("input");
writer.WriteValue("value");
writer.WritePropertyName("output");
writer.WriteValue("result");
writer.WriteEndObject();
writer.Flush();
string jsonText = sw.GetStringBuilder().ToString();
Console.WriteLine(jsonText);
登录后复制

(3)使用JObject读写字符串:

JObject jo = JObject.Parse(jsonText);
string[] values = jo.Properties().Select(item => item.Value.ToString()).ToArray();
登录后复制

(4)使用JsonSerializer读写对象(基于JsonWriter与JsonReader):

数组型数据

string jsonArrayText1 = "[{&#39;a&#39;:&#39;a1&#39;,&#39;b&#39;:&#39;b1&#39;},{&#39;a&#39;:&#39;a2&#39;,&#39;b&#39;:&#39;b2&#39;}]";
JArray ja = (JArray)JsonConvert.DeserializeObject(jsonArrayText1);
string ja1a = ja[1]["a"].ToString();
//或者
JObject o = (JObject)ja[1];
string oa = o["a"].ToString();
登录后复制

嵌套格式

string jsonText = "{\"beijing\":{\"zone\":\"海淀\",\"zone_en\":\"haidian\"}}";
JObject jo = (JObject)JsonConvert.DeserializeObject(jsonText);
string zone = jo["beijing"]["zone"].ToString();
string zone_en = jo["beijing"]["zone_en"].ToString();
登录后复制

自定义类Project

Project p = new Project() { Input = "stone", Output = "gold" };
JsonSerializer serializer = new JsonSerializer();
StringWriter sw = new StringWriter();
serializer.Serialize(new JsonTextWriter(sw), p);
Console.WriteLine(sw.GetStringBuilder().ToString());
StringReader sr = new StringReader(@"{""Input"":""stone"", ""Output"":""gold""}");
Project p1 = (Project)serializer.Deserialize(new JsonTextReader(sr), typeof(Project));
Console.WriteLine(p1.Input + "=>" + p1.Output);
登录后复制

上面的代码都是基于下面这个Project类定义:

class Project
{
    public string Input { get; set; }
    public string Output { get; set; }
}
登录后复制

此外,如果上面的JsonTextReader等类编译不过的话,说明是我们自己修改过的类,换成你们自己的相关类就可以了,不影响使用。

3. 内置方式:使用.NET Framework 3.5/4.0中提供的System.Web.Script.Serialization命名空间下的JavaScriptSerializer类进行对象的序列化与反序列化,很直接。

Project p = new Project() { Input = "stone", Output = "gold" };
 JavaScriptSerializer serializer = new JavaScriptSerializer();
 var json = serializer.Serialize(p);
 Console.WriteLine(json);
 var p1 = serializer.Deserialize<Project>(json);
 Console.WriteLine(p1.Input + "=>" + p1.Output);
 Console.WriteLine(ReferenceEquals(p,p1));
登录后复制

注意:如果使用的是VS2010,则要求当前的工程的Target Framework要改成.Net Framework 4,不能使用Client Profile。当然这个System.Web.Extensions.dll主要是Web使用的,直接在Console工程中用感觉有点浪费资源。

  此外,从最后一句也可以看到,序列化与反序列化是深拷贝的一种典型的实现方式。

4. 契约方式:使用System.Runtime.Serialization.dll提供的DataContractJsonSerializer或者 JsonReaderWriterFactory实现。

Project p = new Project() { Input = "stone", Output = "gold" };
DataContractJsonSerializer serializer = new DataContractJsonSerializer(p.GetType());
string jsonText;
using (MemoryStream stream = new MemoryStream())
{
    serializer.WriteObject(stream, p);
    jsonText = Encoding.UTF8.GetString(stream.ToArray());
    Console.WriteLine(jsonText);
}
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonText)))
{
    DataContractJsonSerializer serializer1 = new DataContractJsonSerializer(typeof(Project));
    Project p1 = (Project)serializer1.ReadObject(ms);
    Console.WriteLine(p1.Input + "=>" + p1.Output);
}
登录后复制

这里要注意,这里的Project类和成员要加相关的Attribute:

[DataContract]
class Project
{
    [DataMember]
    public string Input { get; set; }
    [DataMember]
    public string Output { get; set; }
}
登录后复制

更多C#解析json文件的实现代码相关文章请关注PHP中文网!

Electron Markdown编辑器实战:资源管理器实现

Electron Markdown编辑器实战:资源管理器实现

前言

公众号:【可乐前端】,期待关注交流,分享一些有意思的前端知识

平时记笔记的时候一般使用VsCodemarkdown,不过在本地写的话上传图片就需要搭配图床来使用,不然的话这种markdown发布到其他地方就有问题。想着能折腾就折腾的心态,干脆就自己实现一个好了。需要实现的功能如下:

  1. 资源管理器——包括文件/文件夹的:

    • 创建
    • 删除
    • 复制
    • 移动
    • 重命名
  2. 接入掘金markdown编辑器组件并拓展,数据同步到本地
  3. 基于GitHub实现图床,配合CDN加速

这篇文章会详细介绍第一点,话不多说,让我们立刻开始~

读取本地资源

首先刚打开编辑器的时候需要用户选择一个文件夹,可以把这个文件夹理解为后续操作的根目录。

image.png

选择文件夹这里用到了electrondialog模块,使用dialog.showOpenDialog这个api就可以唤起本地的文件对话框,让用户选择文件或者文件夹。拿到选择的文件夹之后,需要递归获取下面的子文件夹和文件,由于我们实现的是一个markdown编辑器,所以我在取文件的时候只取了md文件。组装成数结构之后传给渲染进程,然后渲染进程再以树的形式渲染出来。

渲染进程给主进程发送一个事件,这个IPC通信以及搭建这个项目的脚手架在之前的文章Electron打造你自己的录屏软件介绍过,感兴趣的同学可以点进去看看,这里就不多赘述。

  const ipcRenderer = window.electron.ipcRenderer
  const handleSelectFolder = () => {
    ipcRenderer.send(OPEN_FILE_DIALOG)
  }

在主进程中监听这个事件,在用户选择完文件夹后,可以拿到文件夹的路径。然后实现一个generateTree函数,把获取到的根目录递归处理,组装成一个树结构。

import { dialog, ipcMain } from ''electron''
import { OPEN_FILE_DIALOG, SELECTED_DIRECTORY } from ''../event''
import fs from ''fs''
import path from ''path''
export default () => {
  ipcMain.on(OPEN_FILE_DIALOG, (event) => {
    dialog
      .showOpenDialog({
        properties: [''openDirectory'']
      })
      .then((result) => {
        if (!result.canceled) {
          const directoryPath = result.filePaths[0]
          const generateTree = (dir) => {
            const items = fs.readdirSync(dir, { withFileTypes: true })
            return items
              .map((item) => {
                const fullPath = path.join(dir, item.name)
                if (item.isDirectory()) {
                  return {
                    title: item.name,
                    key: fullPath,
                    children: generateTree(fullPath)
                  }
                } else if (item.isFile() && item.name.endsWith(''.md'')) {
                  return {
                    title: item.name,
                    key: fullPath,
                    isLeaf: true//后续根据这个判断是不是文件
                  }
                }
              })
              .filter(Boolean)
          }

          let folderTreeData = generateTree(directoryPath)
          folderTreeData = [
            { title: path.basename(directoryPath), key: directoryPath, children: folderTreeData }
          ]
          event.sender.send(SELECTED_DIRECTORY, { folderTreeData, directoryPath })
        }
      })
      .catch((err) => {
        console.log(err)
      })
  })
}

然后渲染进程再监听SELECTED_DIRECTORY这个事件,拿到组装好的结构配合AntdTree组件就可以很快的把一棵树渲染出来。

  ipcRenderer.on(SELECTED_DIRECTORY, (event, data) => {
    const { directoryPath, folderTreeData } = data
    if (folderTreeData.length === 0) {
      message.info(''文件夹为空,请重新选择'')
      return
    }
    setCurrentPath(directoryPath)
    setTreeData(folderTreeData)
  })
  
  // ...
  
  <DirectoryTree
    defaultExpandedKeys={[currentPath]}
    rootClassName={styles.folderTree}
    treeData={treeData}
    onRightClick={handleRightClick}
  />

image.png

右键菜单

这里我是做了一个右键菜单,来承载文件夹树的各个操作。

image.png

Tree组件本身也提供了右键点击事件,而配合AntdDropdown组件的右键触发就可以实现一个右键菜单。但是常规的做法来做的话就要给每一个树节点都需要被一个Dropdown包裹组件,必然会产生一定的性能开销。这里我只用了一个Dropdown组件,只需要动态调整被包裹组件的位置,也可以实现每一个节点的右键菜单功能。

这里树节点的右键点击操作,我们来看看它做了什么:

  const [rightMenus, setRightMenus] = useState([])
  const handleRightClick = ({ event, node }) => {
    const overlay = rightTriggerRef.current
    const { pageX, pageY } = event
    overlay.style.left = `${pageX}px`
    overlay.style.top = `${pageY}px`
    setSelectNode(node)
    setTimeout(() => {
      // overlay
      const event = new MouseEvent(''contextmenu'', {
        bubbles: true,
        cancelable: true,
        view: window,
        button: 2, // 2 表示右键
        // 如果需要设置鼠标坐标,可以添加以下属性
        clientX: pageX,
        clientY: pageY
      })

      }
      const items = [//...]
      setRightMenus(items)
      // 触发右键事件
      overlay.dispatchEvent(event)
    })
  }
  
//...
 <Dropdown menu={{ items: rightMenus }} trigger={[''contextMenu'']}>
    <div ref={rightTriggerRef} style={{ position: ''absolute'' }}></div>
  </Dropdown>

当右键点击树节点时,可以从event对象中获取到鼠标的坐标位置,同时可以获取到触发的节点对象。这个时候根据鼠标的坐标位置动态设置rightTriggerRef的位置,设置完之后再对这个div创建并触发一个右键事件,那么Dropdown就会被触发了。

创建

点击创建菜单项的时候会弹出一个弹窗,输入完标题点击确定之后就会真正走创建的逻辑。

image.png

const path = selectNode.key
ipcRenderer.send(action, path, title, treeData, selectNode)

点击确定之后渲染进程会向主进程发一个事件,介绍一下上面的几个参数:

创建文件

Kapture 2024-02-22 at 00.50.58.gif

主进程接收到这个事件之后,path参数就是即将被创建的文件的父文件夹路径(因为我们只能在文件夹下创建文件)。我们可以拼出新文件的路径,然后通过fs检查一下这个文件名是否已经存在了,如果不存在的话就通过writeFile创建。


import { sep, join } from ''path''
ipcMain.on(ADD_FILE, (event, path, title, oldData) => {
    const newPath = `${path}${sep}${title}.md`
    const exists = fs.existsSync(newPath)
    if (exists) {
      event.sender.send(COMMON_ERROR, ''文件已存在'')
      return
    }
    fs.writeFile(newPath, '''', { encoding: ''utf8'' }, (err) => {
      if (!err) {
        const newData = [...oldData]
        addChildNode(newData, path, { title: `${title}.md`, key: newPath, isLeaf: true })
        event.sender.send(UPDATE_TREE, newData)
      } else {
        console.log(''err'', err)
        event.sender.send(COMMON_ERROR_LOG, err)
      }
    })
})

注意一点writeFile创建完之后,文件确实在文件系统存在了,但是在页面上的文件树还没有更新,我们得把新创建的节点添加到treeData中,主要关注的是addChildNode这个函数。

// 给定一个父节点 key,在父节点下增加子节点
export const addChildNode = (treeData, parentKey, newNode) => {
  const parentNode = findNodeByKey(treeData, parentKey)
  if (parentNode) {
    parentNode.children.push(newNode)
  } else {
    console.error(''Parent node with key'', parentKey, ''not found.'')
  }
}

// 给定一个节点 key 找到该节点
export const findNodeByKey = (nodes, key) => {
  for (let node of nodes) {
    if (node.key === key) {
      return node
    }
    if (node.children) {
      const foundNode = findNodeByKey(node.children, key)
      if (foundNode) {
        return foundNode
      }
    }
  }
  return null
}

上面两个辅助函数帮我们在指定的父节点下插入子节点,插入完成之后通知渲染进程更新即可。

创建文件夹

Kapture 2024-02-22 at 00.49.38.gif

创建文件夹的流程基本一样,只不过是创建的APIwriteFile换成了mkdir而已,具体代码如下:

ipcMain.on(ADD_FOLDER, (event, path, title, oldData) => {
    const newPath = `${path}${sep}${title}`
    const exists = fs.existsSync(newPath)
    if (exists) {
      event.sender.send(COMMON_ERROR, ''文件夹已存在'')
      return
    }
    fs.mkdir(newPath, {}, (err) => {
      if (!err) {
        const newData = [...oldData]
        addChildNode(newData, path, { title: `${title}`, key: newPath, children: [] })
        event.sender.send(UPDATE_TREE, newData)
      } else {
        console.log(''err'', err)
        event.sender.send(COMMON_ERROR_LOG, err)
      }
    })
})

重命名

Kapture 2024-02-22 at 00.51.48.gif

重命名操作的交互跟创建的交互是一样,主要也是关注标题就可以了。来看一下重命名具体做的事情。需要根据文件/文件夹的完整路径中找出旧名字,然后把新名字拼在完整路径上就可以。这里可以根据分割符path.sep来分割完整路径,最后一项就是旧名字。

  ipcMain.on(RENAME, (event, path, title, oldData, selectNode) => {
    const arr = path.split(sep)
    arr.pop()
    if (selectNode.isLeaf) {
      arr.push(`${title}.md`)
    } else {
      arr.push(title)
    }
    let newPath = `${sep}${join(...arr)}`
    const exists = fs.existsSync(newPath)
    if (exists) {
      event.sender.send(COMMON_ERROR, ''文件/文件夹重名'')
      return
    }
    fs.rename(path, newPath, (err) => {
      if (err) {
        console.log(''err'', err)
        event.sender.send(COMMON_ERROR_LOG, err)
      } else {
        const newData = [...oldData]
        updateNodeValue(newData, path, ''title'', selectNode.isLeaf ? `${title}.md` : title)
        updateNodeValue(newData, path, ''key'', newPath)
        event.sender.send(UPDATE_TREE, newData)
      }
    })
  })

同理,更新完文件系统的名字之后,我们还需要更新界面的数据。这里实现了一个updateNodeValue函数,来更新指定节点的某一个属性值。这里直接复用findNodeByKey这个函数,注意的是更新完title属性之后也需要更新key属性。

export const updateNodeValue = (treeData, nodeKey, nodeLabel, newValue) => {
  const node = findNodeByKey(treeData, nodeKey)
  if (node) {
    node[nodeLabel] = newValue
  }
}

删除

Kapture 2024-02-22 at 00.53.08.gif

对于删除来说,会弹一个二次确认,点击确定之后就会真正进入删除的流程。删除的时候也可以使用fs模块封装好的api,删除文件的时候用unlinkSync,删除文件夹用rmSync,因为删除文件夹需要递归删除的,即实现类似 rm -rf 的功能,所以要加上一个{ recursive: true }参数。

  const deleteFileOrFolder = (event, path, selectNode, oldData) => {
    try {
      if (selectNode.isLeaf) {
        fs.unlinkSync(path)
      } else {
        fs.rmSync(path, { recursive: true })
      }
      const newData = [...oldData]
      deleteNodeByKey(newData, path)
      event.sender.send(UPDATE_TREE, newData)
    } catch (err) {
      console.log(''err'', err)
      event.sender.send(COMMON_ERROR_LOG, err)
    }
  }

  ipcMain.on(DELETE, deleteFileOrFolder)

删除完文件系统的时候别忘了删除界面上的,这里实现了一个deleteNodeByKey函数。主要也是通过递归找到对应key的节点,然后把它删除。

export const deleteNodeByKey = (treeData, nodeKey) => {
  // 遍历树
  for (let i = 0; i < treeData.length; i++) {
    const node = treeData[i]
    if (node.key === nodeKey) {
      // 如果当前节点是要删除的节点,直接从树的数组中删除该节点
      treeData.splice(i, 1)
      // 删除成功后退出函数
      return
    }
    if (node.children) {
      // 如果当前节点有子节点,递归删除
      deleteNodeByKey(node.children, nodeKey)
    }
  }
}

复制

复制的时候我这里的交互是把源文件/文件夹,复制到某一个文件夹下:

Kapture 2024-02-22 at 00.54.20.gif
所以需要有一棵这样的树来选择目标文件夹,这棵树也是基于treeData构建出来的,取的是treeData的非叶子节点,即文件夹节点。

export const getNonLeafNodesFromArray = (treeData) => {
  const nonLeafNodes = []

  treeData.forEach((tree) => {
    if (tree.children && tree.children.length > 0) {
      // 如果有子节点,则将当前节点加入新树,并递归获取非叶子节点
      nonLeafNodes.push({
        ...tree,
        children: getNonLeafNodesFromArray(tree.children)
      })
    }
  })

  return nonLeafNodes.length > 0 ? nonLeafNodes : []
}

首先复制的时候需要判断目标文件夹下有没有跟源文件同名的,如果有,则需要把名称加上一个唯一标识,为了方便我这里使用的是时间戳。其次,如果复制的是文件夹,文件夹下的文件路径中其实会包含文件夹的路径信息,所以这里也需要同步修改一下。然后判断一下目标文件夹是不是源文件夹的子文件夹,如果是,则中断流程。最后使用fs-extra模块的copySync复制就好了,复制完之后调用addChildNode给页面插入新节点。

  import fsExtra from ''fs-extra''
  const copy = (event, path, targetPath, selectNode, oldData) => {
    let newTitle = selectNode.title
    if (fs.existsSync(`${targetPath}/${newTitle}`)) {
      if (selectNode.isLeaf) {
        const extIndex = newTitle.lastIndexOf(''.'')
        newTitle = `${selectNode.title.substring(0, extIndex)}_${Date.now()}.md`
      } else {
        newTitle = `${selectNode.title}_${Date.now()}`
      }
    }

    const newNode = {
      title: newTitle,
      key: `${targetPath}${sep}${newTitle}`
    }
    if (selectNode.isLeaf) {
      newNode.isLeaf = true
    } else {
      newNode.children = cloneDeep(selectNode.children)
      //递归遍历,修改文件夹下的文件路径
      dfs(newNode.children, (node) => {
        node.key = node.key.replace(`${targetPath}/${selectNode.title}`, newNode.key)
      })
    }
    if (!selectNode.isLeaf) {
      // 如果目标文件夹是源文件夹的子文件夹,则不进行复制
      let newDestinationDir = newNode.key

      if (isSubdirectory(path, newDestinationDir)) {
        event.sender.send(COMMON_ERROR, ''目标文件夹是源文件夹的子文件夹,无法复制!'')
        return
      }
      // 使用 fs-extra 的 copy 方法复制文件夹
      try {
        fsExtra.copySync(path, newDestinationDir)
      } catch (error) {
        event.sender.send(COMMON_ERROR_LOG, error)
      }
    } else {
      try {
        const content = fs.readFileSync(path, { encoding: ''utf8'' })
        fs.writeFileSync(newNode.key, content, { encoding: ''utf8'' })
      } catch (error) {}
    }
    const newData = [...oldData]
    addChildNode(newData, targetPath, newNode)
    event.sender.send(UPDATE_TREE, newData)
    return { data: newData, success: true }
  }

  ipcMain.on(COPY, copy)

移动

Kapture 2024-02-22 at 00.55.23.gif

移动的交互跟复制一样,对于移动这个操作,我这里是这样实现的,先复制一份,然后再把源文件删除。有了上面的铺垫之后就很简单了,5行代码搞定

  ipcMain.on(MOVE, (event, path, targetPath, selectNode, oldData) => {
    const copyRes = copy(event, path, targetPath, selectNode, oldData)
    if (copyRes.success) {
      deleteFileOrFolder(event, path, selectNode, copyRes.data)
    }
  })

最后

刚开始的时候我是想着找一个开源组件来着,结果没找到合适的,就自己实现了一个,做完之后自己感觉是复习了一遍文件操作和树结构操作。后面将会介绍编辑器主体接入与图床实现,如果你觉得有意思的话,点点关注点点赞吧~

我们今天的关于JSON 编辑器实现代码好用的json编辑器的分享就到这里,谢谢您的阅读,如果想了解更多关于Ajax+Json 级联菜单实现代码_json、Asp.Net Core 使用Monaco Editor 实现代码编辑器功能、C#解析json文件的实现代码、Electron Markdown编辑器实战:资源管理器实现的相关信息,可以在本站进行搜索。

本文标签:

上一篇react-diagram 序列化Json解读案例分析(react json schema)

下一篇js实现模拟购物商城案例(模拟购物网站)