【问题标题】:JSON.stringify() function is replacing the file object with an empty oneJSON.stringify() 函数将文件对象替换为空对象
【发布时间】:2018-11-13 09:37:44
【问题描述】:

问题

大家好,谁能帮我解决这个复杂的问题? : 我正在使用 Spring boot v2.0.5 和 React.js v15.6.2、ReactDom v15.6.2、React Bootstrap v0.32.4 创建一个应用程序,并且我的前端和服务器端部分之间的链接是由使用 Spring 注释的 restful web 服务组成的后面和前面的 fetch API。我的 React 组件是按照父子设计模式概念制作的,这意味着:我的一些组件可以是其他一些组件的子级,反之亦然。

它是如何工作的?

我有一个包含列和行的表格,表格中的每一行都有一个唯一的 ID、2 个下拉菜单、1 个文本输入、2 个日期选择器和 1 个文件上传输入,这是导致主要问题的原因;用户可以通过单击“+文档”按钮添加更多与之前具有相同组件的行;每行都有一个唯一的增量Id,类型为number(整数);下拉菜单和输入事件由父组件内的一种方法根据它们的标签名称处理; 我将用户输入的所有数据存储在对象({})的列表([])中。

示例:如果用户只填写第一行;存储在列表状态中的对象将是这样的:

[{id:0,type:"forms",lang:"all",libelle:"some strings",dateBegin:"11-12-2018",dateEnd:"12-12-2018",document:{File(154845)}]

如果用户添加另一行,然后像第一行一样填充它,列表将是这样的:

  [{id:0,type:"forms",lang:"all",libelle:"some strings",dateBegin:"11-12-2018",dateEnd:"12-12-2018",document:{File(154845)},{id:1,type:"howTo",lang:"en",libelle:"some strings",dateBegin:"11-12-2018",dateEnd:"01-01-2019",document:{File(742015)}]

查看此图片以了解表格的外观: Table Demo

Presentational 组件类(主组件的子级)中的表作为代码

class Presentational extends React.Component {

  constructor(props) {
   super(props);

   this.state = {
    docObjList: [],
    element: (
      <FormDocRowItem // this contains the table tbody tds elements..
        id={1}
        handleChanges={this.props.handleChanges}/>)
   };

     this.handleAddDocumentRow = this.handleAddDocumentRow.bind(this);
 }

  // handleAddDocumentRow method
  handleAddDocumentRow(e) {

   const value = e.target.value;
   const name = e.target.name;

   if (name === 'add') {
    let arr = this.state.docObjList; // get the list state

    // assign the new row component
    arr = [...arr, Object.assign({}, this.state.element)]; 

    // set the new list state
    this.setState({docObjList: arr});
   }

   // if name === 'delete' logic..
 }

   // render method
   render() {
          const {handleReset} = this.props;
      return(
       <FormGroup>
              <Form encType="multipart/form-data">
                <Table striped bordered condensed hover>
                  <thead>
                  <tr>
                    <th>id</th>
                    <th>Type</th>
                    <th>Lang</th>
                    <th>Title</th>
                    <th>Date begin</th>
                    <th>Date end</th>
                    <th>+ Document</th>
                    <th>Options</th>
                  </tr>
                  </thead>
                  <tbody>

                  {this.state.element} // this row is required as initialization
                  {
                    this.state.docObjList.map((doc, index) => {
                      // as index in map() starts from 0 and there is an   
                      // already row component above => The index inside the 
                      // table should start from 1 except The key property 
                      // which should know the right index of the function 
                      const id = index+1; 

                      return (
                        <tr key={index}> 
                          <td>
                            {id}
                          </td>
                          <td>
                            <DocumentTypes id={id} handleChange={this.props.handleChanges}/>
                          </td>
                          <td>
                            <DocumentLanguage id={id} handleChange={this.props.handleChanges}/>
                          </td>
                          <td>
                            <DocumentLibelle id={id} handleChange={this.props.handleChanges}/>
                          </td>
                          <td>
                            <FormControl  id={''+id} name="dateBegin" componentClass="input" type="date"
                                         onChange={this.props.handleChanges}/>
                          </td>
                          <td>
                            <FormControl  id={''+id} name="dateEnd" componentClass="input" type="date"
                                         onChange={this.props.handleChanges}/>
                          </td>
                          <td>
                            <Document  id={id} handleChange={this.props.handleChanges}/>
                          </td>
                          {
                            this.state.docObjList.length == index + 1 &&
                            <td>

                              <button type="button" style={{verticalAlign: 'middle', textAlign: 'center'}} id={index + 1}
                                      name="delete"
                                      onClick={this.handleAddDocumentRow}>
                                Delete
                              </button>
                            </td>
                          }
                        </tr>
                      );
                    })
                  }
                  </tbody>
                </Table>
                <button type="button" name="add" onClick={this.handleAddDocumentRow}>+ Document</button>

                <FormGroup>
                  <Button type="reset"
                          style={{marginRight: '20%'}}
                          className="btn-primary"
                          onClick={this.props.handleClickSubmit}>Submit</Button>
                  <Button name="back" onClick={this.props.handleClickSubmit}>Annuler</Button>
                </FormGroup>
              </Form>
       </FormGroup>
        )
  }
}

行组件类(Presentational的子组件)

const FormDocRowItem = (props) => {

  const {id} = props; // the ID here is refering the column that is going to be 
                      // show inside the table not the index of the map function
  return(
    return (
      <tr>
        <td>
          {id}
        </td>
        <td>
          <DocumentTypes id={id} handleChange={this.props.handleChanges}/>
        </td>
        <td>
          <DocumentLanguage id={id} handleChange={this.props.handleChanges}/>
        </td>
        <td>
          <DocumentLibelle id={id} handleChange={this.props.handleChanges}/>
        </td>
        <td>
          <FormControl id={''+id} name="dateBegin" componentClass="input" type="date" onChange={this.props.handleChanges}/>
        </td>
        <td>
          <FormControl id={''+id} name="dateEnd" componentClass="input" type="date" onChange={this.props.handleChanges}/>
        </td>
        <td>
          <Document id={id} handleChange={this.props.handleChanges}/>
        </td>
      </tr>
    );
  }

}

父组件类(主组件)

   constructor(props) {
     this.state ={
       docDataList: [],
       formIsReadyToSubmit: false
     } 

     this.handleSubmit = this.handleSubmit.bind(this); // button submit click
     this.handleReset = this.handleReset.bind(this); // button reset click
     this.fillWithData = this.fillWithData.bind(this); // handle changes

   }

   // handleReset method..

   fillWithData(e) {

    const name = e.target.name; // get the name of the target
    const id = parseInt(e.target.id); // get the id of the target
    let value = e.target.value; // get the value of the target
    let arr = this.state.docDataList; // get the list state

    // if the target is a file upload
    if (name === 'selectDocument') 
      value = e.target.files[0];

    // create properties with null values starting from the first onchange 
    // event handling, to not get a misplaced properties inside the
   // objects of the list state
    arr.map((x) => {
      x.type = x.type ? x.type : null;
      x.lang = x.lang ? x.lang : null;
      x.libelle = x.libelle ? x.libelle : null;
      x.dateBegin = x.dateBegin ? x.dateBegin : null;
      x.dateEnd = x.dateEnd ? x.dateEnd : null;
      x.document = x.document ? x.document : null;
    });

    // if the event target name is not delete
    if (name != 'delete') {
      // check if the object id already exist in the table
      // if it exists, the new value should replace the previous one
      // and not allowed to add a new object to the list state
      if ((arr.find((x) => x.id == id))) {
        // loop through the list state to find the id of the object
        arr.map((x) => {
          if (x.id == id) {
            // helper variable to prevent empty strings
            const val = value != '' ? value : null;

            switch (name) {
              case 'selectType':
                x.type = val;
                break;
              case 'selectLang':
                x.lang = val;
                break;
              case 'libelle':
                x.libelle = val;
                break;
              case 'dateBegin':
                x.dateBegin = val;
                break;
              case 'dateEnd':
                x.dateEnd = val;
                break;
              case 'selectDocument':
                x.document = val;
                break;
            }
          }
        });
        // assign the new list to my docDataList state
        // mentioning that the id of the element already exist
        this.setState({docDataList: arr}, () => {
          console.log(' ID exist; new dataList :', this.state.docDataList);
        });
      }
      // if the id doesn't exist (means that the button +document is clicked)
      else {
        // again, a helper variable as the previous statement 
        const val = value != '' ? value : null;

        this.setState({
          docDataList: [...arr, Object.assign({
            id: id,
            type: name === 'selectType' ? val : null,
            lang: name === 'selectLang' ? val : null,
            libelle: name === 'libelle' ? val : null,
            dateBegin: name === 'dataBegin' ? val : null,
            dateEnd: name === 'dateEnd' ? val : null//,
            //document: name==='selectDocument'? val:null
          })]
        }, () => {
          console.log('ID doesnt exist; new dataList :', this.state.docDataList);
        });
      }
    }
  }

HandleSubmit() 方法(在父组件类中)

// Submit button click handler
  handleSubmit(e) {

      let docDataList = this.state.docDataList;

      // if the user didn't touch any thing on the table rows
      // that means the list is empty and its length = 0
      if (docDataList.length === 0) {
        this.setState({
          alerts: {
            message: 'Please enter your document information ',
            show: true
          }
        });
      }
      // if the user has entered a data on the table row
      else if (docDataList.length > 0) {

        let data = new FormData(); // object which will be sent 

        // check the docDataList before request
        console.log('DocDataList before request:', docDataList); 
        data.append('docDataList', JSON.stringify(docDataList)); 

        fetch('http://localhost:8080/api/files/uploadFile', {
          method: 'POST',
          body: data
        }).then(response => {
          console.log('success document upload', response);
        }).catch(error => {
          console.log('error', error);
        });

        this.setState({
          formIsReadyToSubmit: true, 
          docDataList: [], // reset the list
          alerts: {updateAlert: true} // make an alert
        });
      }

    }

查看当我用数据填充行时控制台显示的内容:CLICK HERE PLEASE

查看请求的响应:CLICK HERE PLEASE

注意:观看这些屏幕截图后,您可能会注意到,有一个额外的数据列表称为“arrContrats”,我没有在我的问题中提及它,因为它没有任何问题;问题在于“docDataList”列表。提前致谢

【问题讨论】:

  • 请阅读:minimal reproducible example。上面有far太多不相关的信息。此外,请不要使用随机格式(块引号、粗体等)。只需简单、清晰、简洁地提出您的问题。更多:How do I ask a good question?
  • 我已经删除了很多代码以使其尽可能简单,但感谢您的建议
  • 恕我直言,我们不需要上述的 80%。再次,请修复格式。如果您不引用,请不要使用引用标记。 ALL CAPS 大喊大叫,这对获得人们的帮助会适得其反。不必要的黑体字只会使您的问题更难阅读,如果目标是寻求帮助,这又会适得其反。
  • 我只是用引号来突出一些细节
  • 好吧,我照你说的做了

标签: json reactjs spring-boot fetch-api react-bootstrap


【解决方案1】:

如果您的问题是您从浏览器获取File 对象,然后在其上使用JSON.stringify(或包含它的东西)并在JSON 中获取{},这是正确的。浏览器的File 对象没有自己的可枚举属性。 JSON.stringify 只包含自己的、可枚举的属性。

如果您想要File 对象具有的各种属性(继承的访问器属性),您需要将它们复制到一个新对象。

如果你想要文件数据,它不能作为对象上的属性访问,你必须使用它从Blob继承的方法之一来读取文件数据,例如@ 987654322@、textarrayBuffer(或者,您可以使用 FileReader,但没有必要在没有现代方法的过时环境中除外)。

【讨论】:

  • 我已经尝试使用 FileReader 对象但仍然没有得到正确的结果,它只是复制文件的目录路径,如“C://fakepath/
  • @F.Regayeg - 那将是一个不同的问题,看起来你没有正确使用FileReader (this answer may help)。但是上面的答案回答了这个问题:为什么File 对象被字符串化为{}
  • @F.Regayeg - 所有应有的尊重,是的,确实如此。如果您在代码中使用它时遇到问题,我们深表歉意。
  • 您对使用 FileReader 的看法是对的,但不仅如此!您错过了告诉我有关 base64 编码的信息!所以文件可以作为字符串化编码文件发送,最后现在我解决了我的问题,在我的服务器中接收到那个 base64 值后,我只是使用一些 Java 工具对其进行解码
  • @ArtiomO - 要么读取文件内容并将它们放入 JSON(可能不是您最好的选择),要么将 File 对象包含在您发送的 FormData 中(不涉及 JSON)。
【解决方案2】:
function stringifyFileObject(arrWithFiles = []) {
  const arrData = [];
   for (let i=0; i < arrWithFiles.length; i++) {
    const file = arrWithFiles[i];
    const obj = {
        lastModified: file.lastModified,
        name: file.name,
        size: file.size,
        type: file.type,
        webkitRelativePath: file.webkitRelativePath,
    }
    arrData.push( obj );
  }
}

或任何适合您需求的东西。你明白了……

【讨论】:

    【解决方案3】:

    不要将JSON.stringify()FormData 实例一起使用。如果您要发送文件,请不要使用headers: {'Content-Type': 'application/json'}

    例子:

    let data = new FormData(form);
    
    let config = {
      method: "POST",
      body: data
    }
    
    const response = await fetch(url, config);
    const responseData = response.json();
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-09
      • 2018-07-06
      • 1970-01-01
      • 1970-01-01
      • 2014-03-19
      • 2020-04-16
      • 2020-06-15
      • 2016-03-11
      相关资源
      最近更新 更多